/** @file * * VirtualBox COM class implementation */ /* * Copyright (C) 2006-2007 innotek 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 (GPL) 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. */ #include "HardDiskImpl.h" #include "ProgressImpl.h" #include "VirtualBoxImpl.h" #include "SystemPropertiesImpl.h" #include "Logging.h" #include #include #include #include #include #include #include #include #include #define CHECK_BUSY() \ do { \ if (isBusy()) \ return setError (E_UNEXPECTED, \ tr ("Hard disk '%ls' is being used by another task"), \ toString().raw()); \ } while (0) #define CHECK_BUSY_AND_READERS() \ do { \ if (readers() > 0 || isBusy()) \ return setError (E_UNEXPECTED, \ tr ("Hard disk '%ls' is being used by another task"), \ toString().raw()); \ } while (0) /** Task structure for asynchronous VDI operations */ struct VDITask { enum Op { CreateDynamic, CreateStatic, CloneToImage }; VDITask (Op op, HVirtualDiskImage *i, Progress *p) : operation (op) , vdi (i) , progress (p) {} Op operation; ComObjPtr vdi; ComObjPtr progress; /* for CreateDynamic, CreateStatic */ uint64_t size; /* for CloneToImage */ ComObjPtr source; }; /** * Progress callback handler for VDI operations. * * @param uPercent Completetion precentage (0-100). * @param pvUser Pointer to the Progress instance. */ static DECLCALLBACK(int) progressCallback (PVM /* pVM */, unsigned uPercent, void *pvUser) { Progress *progress = static_cast (pvUser); /* update the progress object, capping it at 99% as the final percent * is used for additional operations like setting the UUIDs and similar. */ if (progress) progress->notifyProgress (RT_MIN (uPercent, 99)); return VINF_SUCCESS; } //////////////////////////////////////////////////////////////////////////////// // HardDisk class //////////////////////////////////////////////////////////////////////////////// // constructor / destructor //////////////////////////////////////////////////////////////////////////////// /** Shold be called by subclasses from #FinalConstruct() */ HRESULT HardDisk::FinalConstruct() { mRegistered = FALSE; mStorageType = HardDiskStorageType_VirtualDiskImage; mType = HardDiskType_Normal; mBusy = false; mReaders = 0; return S_OK; } /** * Shold be called by subclasses from #FinalRelease(). * Uninitializes this object by calling #uninit() if it's not yet done. */ void HardDisk::FinalRelease() { uninit(); } // protected initializer/uninitializer for internal purposes only //////////////////////////////////////////////////////////////////////////////// /** * Initializes the hard disk object. * * Subclasses should call this or any other #init() method from their * init() implementations. * * @note * This method doesn't do |isReady()| check and doesn't call * |setReady (true)| on success! * @note * This method must be called from under the object's lock! */ HRESULT HardDisk::protectedInit (VirtualBox *aVirtualBox, HardDisk *aParent) { LogFlowThisFunc (("aParent=%p\n", aParent)); ComAssertRet (aVirtualBox, E_INVALIDARG); mVirtualBox = aVirtualBox; mParent = aParent; if (!aParent) aVirtualBox->addDependentChild (this); else aParent->addDependentChild (this); return S_OK; } /** * Uninitializes the instance. * Subclasses should call this from their uninit() implementations. * The readiness flag must be true on input and will be set to false * on output. * * @param alock this object's autolock * * @note * Using mParent and mVirtualBox members after this method returns * is forbidden. */ void HardDisk::protectedUninit (AutoLock &alock) { LogFlowThisFunc (("\n")); Assert (alock.belongsTo (this)); Assert (isReady()); /* uninit all children */ uninitDependentChildren(); setReady (false); if (mParent) mParent->removeDependentChild (this); else { alock.leave(); mVirtualBox->removeDependentChild (this); alock.enter(); } mParent.setNull(); mVirtualBox.setNull(); } // IHardDisk properties ///////////////////////////////////////////////////////////////////////////// STDMETHODIMP HardDisk::COMGETTER(Id) (GUIDPARAMOUT aId) { if (!aId) return E_POINTER; AutoReaderLock alock (this); CHECK_READY(); mId.cloneTo (aId); return S_OK; } STDMETHODIMP HardDisk::COMGETTER(StorageType) (HardDiskStorageType_T *aStorageType) { if (!aStorageType) return E_POINTER; AutoReaderLock alock (this); CHECK_READY(); *aStorageType = mStorageType; return S_OK; } STDMETHODIMP HardDisk::COMGETTER(Location) (BSTR *aLocation) { if (!aLocation) return E_POINTER; AutoReaderLock alock (this); CHECK_READY(); toString (false /* aShort */).cloneTo (aLocation); return S_OK; } STDMETHODIMP HardDisk::COMGETTER(Type) (HardDiskType_T *aType) { if (!aType) return E_POINTER; AutoReaderLock alock (this); CHECK_READY(); *aType = mType; return S_OK; } STDMETHODIMP HardDisk::COMSETTER(Type) (HardDiskType_T aType) { AutoLock alock (this); CHECK_READY(); if (mRegistered) return setError (E_FAIL, tr ("You cannot change the type of the registered hard disk '%ls'"), toString().raw()); /* return silently if nothing to do */ if (mType == aType) return S_OK; if (mStorageType == HardDiskStorageType_VMDKImage) return setError (E_FAIL, tr ("Currently, changing the type of VMDK hard disks is not allowed")); if (mStorageType == HardDiskStorageType_ISCSIHardDisk) return setError (E_FAIL, tr ("Currently, changing the type of iSCSI hard disks is not allowed")); /// @todo (dmik) later: allow to change the type on any registered hard disk // depending on whether it is attached or not, has children etc. // Don't forget to save hdd configuration afterwards. mType = aType; return S_OK; } STDMETHODIMP HardDisk::COMGETTER(Parent) (IHardDisk **aParent) { if (!aParent) return E_POINTER; AutoReaderLock alock (this); CHECK_READY(); mParent.queryInterfaceTo (aParent); return S_OK; } STDMETHODIMP HardDisk::COMGETTER(Children) (IHardDiskCollection **aChildren) { if (!aChildren) return E_POINTER; AutoReaderLock lock(this); CHECK_READY(); AutoReaderLock chLock (childrenLock()); ComObjPtr collection; collection.createObject(); collection->init (children()); collection.queryInterfaceTo (aChildren); return S_OK; } STDMETHODIMP HardDisk::COMGETTER(Root) (IHardDisk **aRoot) { if (!aRoot) return E_POINTER; AutoReaderLock lock(this); CHECK_READY(); root().queryInterfaceTo (aRoot); return S_OK; } STDMETHODIMP HardDisk::COMGETTER(Accessible) (BOOL *aAccessible) { if (!aAccessible) return E_POINTER; AutoLock alock (this); CHECK_READY(); HRESULT rc = getAccessible (mLastAccessError); if (FAILED (rc)) return rc; *aAccessible = mLastAccessError.isNull(); return S_OK; } STDMETHODIMP HardDisk::COMGETTER(AllAccessible) (BOOL *aAllAccessible) { if (!aAllAccessible) return E_POINTER; AutoLock alock (this); CHECK_READY(); if (mParent) { HRESULT rc = S_OK; /* check the accessibility state of all ancestors */ ComObjPtr parent = (HardDisk *) mParent; while (parent) { AutoLock parentLock (parent); HRESULT rc = parent->getAccessible (mLastAccessError); if (FAILED (rc)) break; *aAllAccessible = mLastAccessError.isNull(); if (!*aAllAccessible) break; parent = parent->mParent; } return rc; } HRESULT rc = getAccessible (mLastAccessError); if (FAILED (rc)) return rc; *aAllAccessible = mLastAccessError.isNull(); return S_OK; } STDMETHODIMP HardDisk::COMGETTER(LastAccessError) (BSTR *aLastAccessError) { if (!aLastAccessError) return E_POINTER; AutoReaderLock alock (this); CHECK_READY(); mLastAccessError.cloneTo (aLastAccessError); return S_OK; } STDMETHODIMP HardDisk::COMGETTER(MachineId) (GUIDPARAMOUT aMachineId) { if (!aMachineId) return E_POINTER; AutoReaderLock alock (this); CHECK_READY(); mMachineId.cloneTo (aMachineId); return S_OK; } STDMETHODIMP HardDisk::COMGETTER(SnapshotId) (GUIDPARAMOUT aSnapshotId) { if (!aSnapshotId) return E_POINTER; AutoReaderLock alock (this); CHECK_READY(); mSnapshotId.cloneTo (aSnapshotId); return S_OK; } STDMETHODIMP HardDisk::CloneToImage (INPTR BSTR aFilePath, IVirtualDiskImage **aImage, IProgress **aProgress) { if (!aFilePath || !(*aFilePath)) return E_INVALIDARG; if (!aImage || !aProgress) return E_POINTER; AutoLock alock (this); CHECK_READY(); CHECK_BUSY(); if (!mParent.isNull()) return setError (E_FAIL, tr ("Cloning differencing VDI images is not yet supported ('%ls')"), toString().raw()); HRESULT rc = S_OK; /* create a project object */ ComObjPtr progress; progress.createObject(); rc = progress->init (mVirtualBox, static_cast (this), Bstr (tr ("Creating a hard disk clone")), FALSE /* aCancelable */); CheckComRCReturnRC (rc); /* create an imageless resulting object */ ComObjPtr image; image.createObject(); rc = image->init (mVirtualBox, NULL, NULL); CheckComRCReturnRC (rc); /* append the default path if only a name is given */ Bstr path = aFilePath; { Utf8Str fp = aFilePath; if (!RTPathHavePath (fp)) { AutoReaderLock propsLock (mVirtualBox->systemProperties()); path = Utf8StrFmt ("%ls%c%s", mVirtualBox->systemProperties()->defaultVDIFolder().raw(), RTPATH_DELIMITER, fp.raw()); } } /* set the desired path */ rc = image->setFilePath (path); CheckComRCReturnRC (rc); /* ensure the directory exists */ { Utf8Str imageDir = image->filePath(); RTPathStripFilename (imageDir.mutableRaw()); if (!RTDirExists (imageDir)) { int vrc = RTDirCreateFullPath (imageDir, 0777); if (VBOX_FAILURE (vrc)) { return setError (E_FAIL, tr ("Could not create a directory '%s' " "to store the image file (%Vrc)"), imageDir.raw(), vrc); } } } /* mark as busy (being created) * (VDI task thread will unmark it) */ image->setBusy(); /* fill in a VDI task data */ VDITask *task = new VDITask (VDITask::CloneToImage, image, progress); task->source = this; /* increase readers until finished * (VDI task thread will decrease them) */ addReader(); /* create the hard disk creation thread, pass operation data */ int vrc = RTThreadCreate (NULL, HVirtualDiskImage::VDITaskThread, (void *) task, 0, RTTHREADTYPE_MAIN_HEAVY_WORKER, 0, "VDITask"); ComAssertMsgRC (vrc, ("Could not create a thread (%Vrc)", vrc)); if (VBOX_FAILURE (vrc)) { releaseReader(); image->clearBusy(); delete task; return E_FAIL; } /* return interfaces to the caller */ image.queryInterfaceTo (aImage); progress.queryInterfaceTo (aProgress); return S_OK; } // public methods for internal purposes only ///////////////////////////////////////////////////////////////////////////// /** * Returns the very first (grand-) parent of this hard disk or the hard * disk itself, if it doesn't have a parent. * * @note * Must be called from under the object's lock */ ComObjPtr HardDisk::root() const { ComObjPtr root = const_cast (this); ComObjPtr parent; while ((parent = root->parent())) root = parent; return root; } /** * Attempts to mark the hard disk as registered. * Must be always called by every reimplementation. * Only VirtualBox can call this method. * * @param aRegistered true to set registered and false to set unregistered */ HRESULT HardDisk::trySetRegistered (BOOL aRegistered) { AutoLock alock (this); CHECK_READY(); if (aRegistered) { ComAssertRet (mMachineId.isEmpty(), E_FAIL); ComAssertRet (mId && children().size() == 0, E_FAIL); if (mRegistered) return setError (E_FAIL, tr ("Hard disk '%ls' is already registered"), toString().raw()); CHECK_BUSY(); } else { if (!mRegistered) return setError (E_FAIL, tr ("Hard disk '%ls' is already unregistered"), toString().raw()); if (!mMachineId.isEmpty()) return setError (E_FAIL, tr ("Hard disk '%ls' is attached to a virtual machine with UUID {%s}"), toString().raw(), mMachineId.toString().raw()); if (children().size() > 0) return setError (E_FAIL, tr ("Hard disk '%ls' has %d differencing hard disks based on it"), toString().raw(), children().size()); CHECK_BUSY_AND_READERS(); } mRegistered = aRegistered; return S_OK; } /** * Checks basic accessibility of this hard disk only (w/o parents). * Must be always called by every HardDisk::getAccessible() reimplementation * in the first place. * * When @a aCheckBusy is true, this method checks that mBusy = false (and * returns an appropriate error if not). This lets reimplementations * successfully call addReader() after getBaseAccessible() succeeds to * reference the disk and protect it from being modified or deleted before * the remaining check steps are done. Note that in this case, the * reimplementation must enter the object lock before calling this method and * must not leave it before calling addReader() to avoid race condition. * * When @a aCheckReaders is true, this method checks that mReaders = 0 (and * returns an appropriate error if not). When set to true together with * @a aCheckBusy, this lets reimplementations successfully call setBusy() after * getBaseAccessible() succeeds to lock the disk and make sure nobody is * referencing it until the remaining check steps are done. Note that in this * case, the reimplementation must enter the object lock before calling this * method and must not leave it before calling setBusy() to avoid race * condition. * * @param aAccessError On output, a null string indicates the hard disk is * accessible, otherwise contains a message describing * the reason of inaccessibility. * @param aCheckBusy Whether to do the busy check or not. * @param aCheckReaders Whether to do readers check or not. */ HRESULT HardDisk::getBaseAccessible (Bstr &aAccessError, bool aCheckBusy /* = false */, bool aCheckReaders /* = false */) { AutoReaderLock alock (this); CHECK_READY(); aAccessError.setNull(); if (aCheckBusy) { if (mBusy) { aAccessError = Utf8StrFmt ( tr ("Hard disk '%ls' is being exclusively used by another task"), toString().raw()); return S_OK; } } if (aCheckReaders) { if (mReaders > 0) { aAccessError = Utf8StrFmt ( tr ("Hard disk '%ls' is being used by another task (%d readers)"), toString().raw(), mReaders); return S_OK; } } return S_OK; } /** * Returns true if the set of properties that makes this object unique * is equal to the same set of properties in the given object. */ bool HardDisk::sameAs (HardDisk *that) { AutoReaderLock alock (this); if (!isReady()) return false; /// @todo (dmik) besides UUID, we temporarily use toString() to uniquely // identify objects. This is ok for VDIs but may be not good for iSCSI, // so it will need a reimp of this method. return that->mId == mId || toString (false /* aShort */) == that->toString (false /* aShort */); } /** * Marks this hard disk as busy. * A busy hard disk cannot have readers and its properties (UUID, description) * cannot be externally modified. */ void HardDisk::setBusy() { AutoLock alock (this); AssertReturnVoid (isReady()); AssertMsgReturnVoid (mBusy == false, ("%ls", toString().raw())); AssertMsgReturnVoid (mReaders == 0, ("%ls", toString().raw())); mBusy = true; } /** * Clears the busy flag previously set by #setBusy(). */ void HardDisk::clearBusy() { AutoLock alock (this); AssertReturnVoid (isReady()); AssertMsgReturnVoid (mBusy == true, ("%ls", toString().raw())); mBusy = false; } /** * Increases the number of readers of this hard disk. * A hard disk that have readers cannot be marked as busy (and vice versa) * and its properties (UUID, description) cannot be externally modified. */ void HardDisk::addReader() { AutoLock alock (this); AssertReturnVoid (isReady()); AssertMsgReturnVoid (mBusy == false, ("%ls", toString().raw())); ++ mReaders; } /** * Decreases the number of readers of this hard disk. */ void HardDisk::releaseReader() { AutoLock alock (this); AssertReturnVoid (isReady()); AssertMsgReturnVoid (mBusy == false, ("%ls", toString().raw())); AssertMsgReturnVoid (mReaders > 0, ("%ls", toString().raw())); -- mReaders; } /** * Increases the number of readers on all ancestors of this hard disk. */ void HardDisk::addReaderOnAncestors() { AutoLock alock (this); AssertReturnVoid (isReady()); if (mParent) { AutoLock alock (mParent); mParent->addReader(); mParent->addReaderOnAncestors(); } } /** * Decreases the number of readers on all ancestors of this hard disk. */ void HardDisk::releaseReaderOnAncestors() { AutoLock alock (this); AssertReturnVoid (isReady()); if (mParent) { AutoLock alock (mParent); mParent->releaseReaderOnAncestors(); mParent->releaseReader(); } } /** * Returns true if this hard disk has children not belonging to the same * machine. */ bool HardDisk::hasForeignChildren() { AutoReaderLock alock (this); AssertReturn (isReady(), false); AssertReturn (!mMachineId.isEmpty(), false); /* check all children */ AutoReaderLock chLock (childrenLock()); for (HardDiskList::const_iterator it = children().begin(); it != children().end(); ++ it) { ComObjPtr child = *it; AutoReaderLock childLock (child); if (child->mMachineId != mMachineId) return true; } return false; } /** * Marks this hard disk and all its children as busy. * Used for merge operations. * Returns a meaningful error info on failure. */ HRESULT HardDisk::setBusyWithChildren() { AutoLock alock (this); AssertReturn (isReady(), E_FAIL); const char *errMsg = tr ("Hard disk '%ls' is being used by another task"); if (mReaders > 0 || mBusy) return setError (E_FAIL, errMsg, toString().raw()); AutoReaderLock chLock (childrenLock()); for (HardDiskList::const_iterator it = children().begin(); it != children().end(); ++ it) { ComObjPtr child = *it; AutoLock childLock (child); if (child->mReaders > 0 || child->mBusy) { /* reset the busy flag of all previous children */ while (it != children().begin()) (*(-- it))->clearBusy(); return setError (E_FAIL, errMsg, child->toString().raw()); } else child->mBusy = true; } mBusy = true; return S_OK; } /** * Clears the busy flag of this hard disk and all its children. * An opposite to #setBusyWithChildren. */ void HardDisk::clearBusyWithChildren() { AutoLock alock (this); AssertReturn (isReady(), (void) 0); AssertReturn (mBusy == true, (void) 0); AutoReaderLock chLock (childrenLock()); for (HardDiskList::const_iterator it = children().begin(); it != children().end(); ++ it) { ComObjPtr child = *it; AutoLock childLock (child); Assert (child->mBusy == true); child->mBusy = false; } mBusy = false; } /** * Checks that this hard disk and all its direct children are accessible. * * @note Locks this object for writing. */ HRESULT HardDisk::getAccessibleWithChildren (Bstr &aAccessError) { /* getAccessible() needs a write lock */ AutoLock alock (this); AssertReturn (isReady(), E_FAIL); HRESULT rc = getAccessible (aAccessError); if (FAILED (rc) || !aAccessError.isNull()) return rc; AutoReaderLock chLock (childrenLock()); for (HardDiskList::const_iterator it = children().begin(); it != children().end(); ++ it) { ComObjPtr child = *it; rc = child->getAccessible (aAccessError); if (FAILED (rc) || !aAccessError.isNull()) return rc; } return rc; } /** * Checks that this hard disk and all its descendants are consistent. * For now, the consistency means that: * * 1) every differencing image is associated with a registered machine * 2) every root image that has differencing children is associated with * a registered machine. * * This method is used by the VirtualBox constructor after loading all hard * disks and all machines. */ HRESULT HardDisk::checkConsistency() { AutoReaderLock alock (this); AssertReturn (isReady(), E_FAIL); if (isDifferencing()) { Assert (mVirtualBox->isMachineIdValid (mMachineId) || mMachineId.isEmpty()); if (mMachineId.isEmpty()) return setError (E_FAIL, tr ("Differencing hard disk '%ls' is not associated with " "any registered virtual machine or snapshot"), toString().raw()); } HRESULT rc = S_OK; AutoReaderLock chLock (childrenLock()); if (mParent.isNull() && mType == HardDiskType_Normal && children().size() != 0) { if (mMachineId.isEmpty()) return setError (E_FAIL, tr ("Hard disk '%ls' is not associated with any registered " "virtual machine or snapshot, but has differencing child " "hard disks based on it"), toString().raw()); } for (HardDiskList::const_iterator it = children().begin(); it != children().end() && SUCCEEDED (rc); ++ it) { rc = (*it)->checkConsistency(); } return rc; } /** * Creates a differencing hard disk for this hard disk and returns the * created hard disk object to the caller. * * The created differencing hard disk is automatically added to the list of * children of this hard disk object and registered within VirtualBox. * The specified progress object (if not NULL) receives the percentage * of the operation completion. However, it is responsibility of the caller to * call Progress::notifyComplete() after this method returns. * * @param aFolder folder where to create the differencing disk * (must be a full path) * @param aMachineId machine ID the new hard disk will belong to * @param aHardDisk resulting hard disk object * @param aProgress progress object to run during copy operation * (may be NULL) * * @note * Must be NOT called from under locks of other objects that need external * access dirung this method execurion! */ HRESULT HardDisk::createDiffHardDisk (const Bstr &aFolder, const Guid &aMachineId, ComObjPtr &aHardDisk, Progress *aProgress) { AssertReturn (!aFolder.isEmpty() && !aMachineId.isEmpty(), E_FAIL); AutoLock alock (this); CHECK_READY(); ComAssertRet (isBusy() == false, E_FAIL); Guid id; id.create(); Utf8Str filePathTo = Utf8StrFmt ("%ls%c{%Vuuid}.vdi", aFolder.raw(), RTPATH_DELIMITER, id.ptr()); /* try to make the path relative to the vbox home dir */ const char *filePathToRel = filePathTo; { const Utf8Str &homeDir = mVirtualBox->homeDir(); if (!strncmp (filePathTo, homeDir, homeDir.length())) filePathToRel = (filePathToRel + homeDir.length() + 1); } /* first ensure the directory exists */ { Utf8Str dir = aFolder; if (!RTDirExists (dir)) { int vrc = RTDirCreateFullPath (dir, 0777); if (VBOX_FAILURE (vrc)) { return setError (E_FAIL, tr ("Could not create a directory '%s' " "to store the image file (%Vrc)"), dir.raw(), vrc); } } } alock.leave(); /* call storage type specific diff creation method */ HRESULT rc = createDiffImage (id, filePathTo, aProgress); alock.enter(); CheckComRCReturnRC (rc); ComObjPtr vdi; vdi.createObject(); rc = vdi->init (mVirtualBox, this, Bstr (filePathToRel), TRUE /* aRegistered */); CheckComRCReturnRC (rc); /* associate the created hard disk with the given machine */ vdi->setMachineId (aMachineId); rc = mVirtualBox->registerHardDisk (vdi, VirtualBox::RHD_Internal); CheckComRCReturnRC (rc); aHardDisk = vdi; return S_OK; } /** * Checks if the given change of \a aOldPath to \a aNewPath affects the path * of this hard disk or any of its children and updates it if necessary (by * calling #updatePath()). Intended to be called only by * VirtualBox::updateSettings() if a machine's name change causes directory * renaming that affects this image. * * @param aOldPath old path (full) * @param aNewPath new path (full) * * @note Locks this object and all children for writing. */ void HardDisk::updatePaths (const char *aOldPath, const char *aNewPath) { AssertReturnVoid (aOldPath); AssertReturnVoid (aNewPath); AutoLock alock (this); AssertReturnVoid (isReady()); updatePath (aOldPath, aNewPath); /* update paths of all children */ AutoReaderLock chLock (childrenLock()); for (HardDiskList::const_iterator it = children().begin(); it != children().end(); ++ it) { (*it)->updatePaths (aOldPath, aNewPath); } } /** * Helper method that deduces a hard disk object type to create from * the location string format and from the contents of the resource * pointed to by the location string. * * Currently, the location string must be a file path which is * passed to the HVirtualDiskImage or HVMDKImage initializer in * attempt to create a hard disk object. * * @param aVirtualBox * @param aLocation * @param hardDisk * * @return */ /* static */ HRESULT HardDisk::openHardDisk (VirtualBox *aVirtualBox, INPTR BSTR aLocation, ComObjPtr &hardDisk) { LogFlowFunc (("aLocation=\"%ls\"\n", aLocation)); AssertReturn (aVirtualBox, E_POINTER); /* null and empty strings are not allowed locations */ AssertReturn (aLocation, E_INVALIDARG); AssertReturn (*aLocation, E_INVALIDARG); static const struct { HardDiskStorageType_T type; const char *ext; } storageTypes[] = { /* try the plugin format first if there is no extension match */ { HardDiskStorageType_CustomHardDisk, NULL }, /* then try the rest */ { HardDiskStorageType_VMDKImage, ".vmdk" }, { HardDiskStorageType_VirtualDiskImage, ".vdi" }, { HardDiskStorageType_VHDImage, ".vhd" }, }; /* try to guess the probe order by extension */ size_t first = 0; bool haveFirst = false; Utf8Str loc = aLocation; char *ext = RTPathExt (loc); for (size_t i = 0; i < ELEMENTS (storageTypes); ++ i) { if (storageTypes [i].ext && RTPathCompare (ext, storageTypes [i].ext) == 0) { first = i; haveFirst = true; break; } } HRESULT rc = S_OK; HRESULT firstRC = S_OK; com::ErrorInfoKeeper firstErr (true /* aIsNull */); for (size_t i = 0; i < ELEMENTS (storageTypes); ++ i) { size_t j = !haveFirst ? i : i == 0 ? first : i == first ? 0 : i; switch (storageTypes [j].type) { case HardDiskStorageType_VirtualDiskImage: { ComObjPtr obj; obj.createObject(); rc = obj->init (aVirtualBox, NULL, aLocation, FALSE /* aRegistered */); if (SUCCEEDED (rc)) { hardDisk = obj; return rc; } break; } case HardDiskStorageType_VMDKImage: { ComObjPtr obj; obj.createObject(); rc = obj->init (aVirtualBox, NULL, aLocation, FALSE /* aRegistered */); if (SUCCEEDED (rc)) { hardDisk = obj; return rc; } break; } case HardDiskStorageType_CustomHardDisk: { ComObjPtr obj; obj.createObject(); rc = obj->init (aVirtualBox, NULL, aLocation, FALSE /* aRegistered */); if (SUCCEEDED (rc)) { hardDisk = obj; return rc; } break; } case HardDiskStorageType_VHDImage: { ComObjPtr obj; obj.createObject(); rc = obj->init (aVirtualBox, NULL, aLocation, FALSE /* aRegistered */); if (SUCCEEDED (rc)) { hardDisk = obj; return rc; } break; } default: { AssertComRCReturnRC (E_FAIL); } } Assert (FAILED (rc)); /* remember the error of the matching class */ if (haveFirst && j == first) { firstRC = rc; firstErr.fetch(); } } if (haveFirst) { Assert (FAILED (firstRC)); /* firstErr will restore the error info upon destruction */ return firstRC; } /* There was no exact extension match; chances are high that an error we * got after probing is useless. Use a generic error message instead. */ firstErr.forget(); return setError (E_FAIL, tr ("Could not recognize the format of the hard disk '%ls'. " "Either the given format is not supported or hard disk data " "is corrupt"), aLocation); } // protected methods ///////////////////////////////////////////////////////////////////////////// /** * Loads the base settings of the hard disk from the given node, registers * it and loads and registers all child hard disks as HVirtualDiskImage * instances. * * Subclasses must call this method in their init() or loadSettings() methods * *after* they load specific parts of data (at least, necessary to let * toString() function correctly), in order to be properly loaded from the * settings file and registered. * * @param aHDNode node when #isDifferencing() = false, or * node otherwise. * * @note * Must be called from under the object's lock */ HRESULT HardDisk::loadSettings (const settings::Key &aHDNode) { using namespace settings; AssertReturn (!aHDNode.isNull(), E_FAIL); /* required */ mId = aHDNode.value ("uuid"); if (!isDifferencing()) { /* type required for nodes only */ const char *type = aHDNode.stringValue ("type"); if (strcmp (type, "normal") == 0) mType = HardDiskType_Normal; else if (strcmp (type, "immutable") == 0) mType = HardDiskType_Immutable; else if (strcmp (type, "writethrough") == 0) mType = HardDiskType_Writethrough; else ComAssertMsgFailedRet (("Invalid hard disk type '%s'\n", type), E_FAIL); } else mType = HardDiskType_Normal; HRESULT rc = mVirtualBox->registerHardDisk (this, VirtualBox::RHD_OnStartUp); CheckComRCReturnRC (rc); /* load all children */ Key::List children = aHDNode.keys ("DiffHardDisk"); for (Key::List::const_iterator it = children.begin(); it != children.end(); ++ it) { Key vdiNode = (*it).key ("VirtualDiskImage"); ComObjPtr vdi; vdi.createObject(); rc = vdi->init (mVirtualBox, this, (*it), vdiNode); CheckComRCBreakRC (rc); } return rc; } /** * Saves the base settings of the hard disk to the given node * and saves all child hard disks as nodes. * * Subclasses must call this method in their saveSettings() methods * in order to be properly saved to the settings file. * * @param aHDNode node when #isDifferencing() = false, or * node otherwise. * * @note * Must be called from under the object's read lock */ HRESULT HardDisk::saveSettings (settings::Key &aHDNode) { using namespace settings; AssertReturn (!aHDNode.isNull(), E_FAIL); /* uuid (required) */ aHDNode.setValue ("uuid", mId); if (!isDifferencing()) { /* type (required) */ const char *type = NULL; switch (mType) { case HardDiskType_Normal: type = "normal"; break; case HardDiskType_Immutable: type = "immutable"; break; case HardDiskType_Writethrough: type = "writethrough"; break; } aHDNode.setStringValue ("type", type); } /* save all children */ AutoReaderLock chLock (childrenLock()); for (HardDiskList::const_iterator it = children().begin(); it != children().end(); ++ it) { ComObjPtr child = *it; AutoReaderLock childLock (child); Key hdNode = aHDNode.appendKey ("DiffHardDisk"); { Key vdiNode = hdNode.createKey ("VirtualDiskImage"); HRESULT rc = child->saveSettings (hdNode, vdiNode); CheckComRCReturnRC (rc); } } return S_OK; } //////////////////////////////////////////////////////////////////////////////// // HVirtualDiskImage class //////////////////////////////////////////////////////////////////////////////// // constructor / destructor //////////////////////////////////////////////////////////////////////////////// HRESULT HVirtualDiskImage::FinalConstruct() { HRESULT rc = HardDisk::FinalConstruct(); if (FAILED (rc)) return rc; mState = NotCreated; mStateCheckSem = NIL_RTSEMEVENTMULTI; mStateCheckWaiters = 0; mSize = 0; mActualSize = 0; return S_OK; } void HVirtualDiskImage::FinalRelease() { HardDisk::FinalRelease(); } // public initializer/uninitializer for internal purposes only //////////////////////////////////////////////////////////////////////////////// // public methods for internal purposes only ///////////////////////////////////////////////////////////////////////////// /** * Initializes the VDI hard disk object by reading its properties from * the given configuration node. The created hard disk will be marked as * registered on success. * * @param aHDNode or node. * @param aVDINode node. */ HRESULT HVirtualDiskImage::init (VirtualBox *aVirtualBox, HardDisk *aParent, const settings::Key &aHDNode, const settings::Key &aVDINode) { using namespace settings; LogFlowThisFunc (("\n")); AssertReturn (!aHDNode.isNull() && !aVDINode.isNull(), E_FAIL); AutoLock alock (this); ComAssertRet (!isReady(), E_UNEXPECTED); mStorageType = HardDiskStorageType_VirtualDiskImage; HRESULT rc = S_OK; do { rc = protectedInit (aVirtualBox, aParent); CheckComRCBreakRC (rc); /* set ready to let protectedUninit() be called on failure */ setReady (true); /* filePath (required) */ Bstr filePath = aVDINode.stringValue ("filePath"); rc = setFilePath (filePath); CheckComRCBreakRC (rc); LogFlowThisFunc (("'%ls'\n", mFilePathFull.raw())); /* load basic settings and children */ rc = loadSettings (aHDNode); CheckComRCBreakRC (rc); mState = Created; mRegistered = TRUE; /* Don't call queryInformation() for registered hard disks to * prevent the calling thread (i.e. the VirtualBox server startup * thread) from an unexpected freeze. The vital mId property (UUID) * is read from the registry file in loadSettings(). To get the rest, * the user will have to call COMGETTER(Accessible) manually. */ } while (0); if (FAILED (rc)) uninit(); return rc; } /** * Initializes the VDI hard disk object using the given image file name. * * @param aVirtualBox VirtualBox parent. * @param aParent Parent hard disk. * @param aFilePath Path to the image file, or @c NULL to create an * image-less object. * @param aRegistered Whether to mark this disk as registered or not * (ignored when @a aFilePath is @c NULL, assuming @c FALSE) */ HRESULT HVirtualDiskImage::init (VirtualBox *aVirtualBox, HardDisk *aParent, const BSTR aFilePath, BOOL aRegistered /* = FALSE */) { LogFlowThisFunc (("aFilePath='%ls', aRegistered=%d\n", aFilePath, aRegistered)); AutoLock alock (this); ComAssertRet (!isReady(), E_UNEXPECTED); mStorageType = HardDiskStorageType_VirtualDiskImage; HRESULT rc = S_OK; do { rc = protectedInit (aVirtualBox, aParent); CheckComRCBreakRC (rc); /* set ready to let protectedUninit() be called on failure */ setReady (true); rc = setFilePath (aFilePath); CheckComRCBreakRC (rc); Assert (mId.isEmpty()); if (aFilePath && *aFilePath) { mRegistered = aRegistered; mState = Created; /* Call queryInformation() anyway (even if it will block), because * it is the only way to get the UUID of the existing VDI and * initialize the vital mId property. */ Bstr errMsg; rc = queryInformation (&errMsg); if (SUCCEEDED (rc)) { /* We are constructing a new HVirtualDiskImage object. If there * is a fatal accessibility error (we cannot read image UUID), * we have to fail. We do so even on non-fatal errors as well, * because it's not worth to keep going with the inaccessible * image from the very beginning (when nothing else depends on * it yet). */ if (!errMsg.isNull()) rc = setErrorBstr (E_FAIL, errMsg); } } else { mRegistered = FALSE; mState = NotCreated; mId.create(); } } while (0); if (FAILED (rc)) uninit(); return rc; } /** * Uninitializes the instance and sets the ready flag to FALSE. * Called either from FinalRelease(), by the parent when it gets destroyed, * or by a third party when it decides this object is no more valid. */ void HVirtualDiskImage::uninit() { LogFlowThisFunc (("\n")); AutoLock alock (this); if (!isReady()) return; HardDisk::protectedUninit (alock); } // IHardDisk properties //////////////////////////////////////////////////////////////////////////////// STDMETHODIMP HVirtualDiskImage::COMGETTER(Description) (BSTR *aDescription) { if (!aDescription) return E_POINTER; AutoReaderLock alock (this); CHECK_READY(); mDescription.cloneTo (aDescription); return S_OK; } STDMETHODIMP HVirtualDiskImage::COMSETTER(Description) (INPTR BSTR aDescription) { AutoLock alock (this); CHECK_READY(); CHECK_BUSY_AND_READERS(); if (mState >= Created) { int vrc = VDISetImageComment (Utf8Str (mFilePathFull), Utf8Str (aDescription)); if (VBOX_FAILURE (vrc)) return setError (E_FAIL, tr ("Could not change the description of the VDI hard disk '%ls' " "(%Vrc)"), toString().raw(), vrc); } mDescription = aDescription; return S_OK; } STDMETHODIMP HVirtualDiskImage::COMGETTER(Size) (ULONG64 *aSize) { if (!aSize) return E_POINTER; AutoReaderLock alock (this); CHECK_READY(); /* only a non-differencing image knows the logical size */ if (isDifferencing()) return root()->COMGETTER(Size) (aSize); *aSize = mSize; return S_OK; } STDMETHODIMP HVirtualDiskImage::COMGETTER(ActualSize) (ULONG64 *aActualSize) { if (!aActualSize) return E_POINTER; AutoReaderLock alock (this); CHECK_READY(); *aActualSize = mActualSize; return S_OK; } // IVirtualDiskImage properties //////////////////////////////////////////////////////////////////////////////// STDMETHODIMP HVirtualDiskImage::COMGETTER(FilePath) (BSTR *aFilePath) { if (!aFilePath) return E_POINTER; AutoReaderLock alock (this); CHECK_READY(); mFilePathFull.cloneTo (aFilePath); return S_OK; } STDMETHODIMP HVirtualDiskImage::COMSETTER(FilePath) (INPTR BSTR aFilePath) { AutoLock alock (this); CHECK_READY(); if (mState != NotCreated) return setError (E_ACCESSDENIED, tr ("Cannot change the file path of the existing hard disk '%ls'"), toString().raw()); CHECK_BUSY_AND_READERS(); /* append the default path if only a name is given */ Bstr path = aFilePath; if (aFilePath && *aFilePath) { Utf8Str fp = aFilePath; if (!RTPathHavePath (fp)) { AutoReaderLock propsLock (mVirtualBox->systemProperties()); path = Utf8StrFmt ("%ls%c%s", mVirtualBox->systemProperties()->defaultVDIFolder().raw(), RTPATH_DELIMITER, fp.raw()); } } return setFilePath (path); } STDMETHODIMP HVirtualDiskImage::COMGETTER(Created) (BOOL *aCreated) { if (!aCreated) return E_POINTER; AutoReaderLock alock (this); CHECK_READY(); *aCreated = mState >= Created; return S_OK; } // IVirtualDiskImage methods ///////////////////////////////////////////////////////////////////////////// STDMETHODIMP HVirtualDiskImage::CreateDynamicImage (ULONG64 aSize, IProgress **aProgress) { if (!aProgress) return E_POINTER; AutoLock alock (this); CHECK_READY(); return createImage (aSize, TRUE /* aDynamic */, aProgress); } STDMETHODIMP HVirtualDiskImage::CreateFixedImage (ULONG64 aSize, IProgress **aProgress) { if (!aProgress) return E_POINTER; AutoLock alock (this); CHECK_READY(); return createImage (aSize, FALSE /* aDynamic */, aProgress); } STDMETHODIMP HVirtualDiskImage::DeleteImage() { AutoLock alock (this); CHECK_READY(); CHECK_BUSY_AND_READERS(); if (mRegistered) return setError (E_ACCESSDENIED, tr ("Cannot delete an image of the registered hard disk image '%ls"), mFilePathFull.raw()); if (mState == NotCreated) return setError (E_FAIL, tr ("Hard disk image has been already deleted or never created")); HRESULT rc = deleteImage(); CheckComRCReturnRC (rc); mState = NotCreated; /* reset the fields */ mSize = 0; mActualSize = 0; return S_OK; } // public/protected methods for internal purposes only ///////////////////////////////////////////////////////////////////////////// /** * Attempts to mark the hard disk as registered. * Only VirtualBox can call this method. */ HRESULT HVirtualDiskImage::trySetRegistered (BOOL aRegistered) { AutoLock alock (this); CHECK_READY(); if (aRegistered) { if (mState == NotCreated) return setError (E_FAIL, tr ("Image file '%ls' is not yet created for this hard disk"), mFilePathFull.raw()); if (isDifferencing()) return setError (E_FAIL, tr ("Hard disk '%ls' is differencing and cannot be unregistered " "explicitly"), mFilePathFull.raw()); } else { ComAssertRet (mState >= Created, E_FAIL); } return HardDisk::trySetRegistered (aRegistered); } /** * Checks accessibility of this hard disk image only (w/o parents). * * @param aAccessError on output, a null string indicates the hard disk is * accessible, otherwise contains a message describing * the reason of inaccessibility. * * @note Locks this object for writing. */ HRESULT HVirtualDiskImage::getAccessible (Bstr &aAccessError) { /* queryInformation() needs a write lock */ AutoLock alock (this); CHECK_READY(); if (mStateCheckSem != NIL_RTSEMEVENTMULTI) { /* An accessibility check in progress on some other thread, * wait for it to finish. */ ComAssertRet (mStateCheckWaiters != (ULONG) ~0, E_FAIL); ++ mStateCheckWaiters; alock.leave(); int vrc = RTSemEventMultiWait (mStateCheckSem, RT_INDEFINITE_WAIT); alock.enter(); AssertReturn (mStateCheckWaiters != 0, E_FAIL); -- mStateCheckWaiters; if (mStateCheckWaiters == 0) { RTSemEventMultiDestroy (mStateCheckSem); mStateCheckSem = NIL_RTSEMEVENTMULTI; } AssertRCReturn (vrc, E_FAIL); /* don't touch aAccessError, it has been already set */ return S_OK; } /* check the basic accessibility */ HRESULT rc = getBaseAccessible (aAccessError, true /* aCheckBusy */); if (FAILED (rc) || !aAccessError.isNull()) return rc; if (mState >= Created) { return queryInformation (&aAccessError); } aAccessError = Utf8StrFmt ("Hard disk image '%ls' is not yet created", mFilePathFull.raw()); return S_OK; } /** * Saves hard disk settings to the specified storage node and saves * all children to the specified hard disk node * * @param aHDNode or node. * @param aStorageNode node. */ HRESULT HVirtualDiskImage::saveSettings (settings::Key &aHDNode, settings::Key &aStorageNode) { AssertReturn (!aHDNode.isNull() && !aStorageNode.isNull(), E_FAIL); AutoReaderLock alock (this); CHECK_READY(); /* filePath (required) */ aStorageNode.setValue ("filePath", mFilePath); /* save basic settings and children */ return HardDisk::saveSettings (aHDNode); } /** * Checks if the given change of \a aOldPath to \a aNewPath affects the path * of this hard disk and updates it if necessary to reflect the new location. * Intended to be from HardDisk::updatePaths(). * * @param aOldPath old path (full) * @param aNewPath new path (full) * * @note Locks this object for writing. */ void HVirtualDiskImage::updatePath (const char *aOldPath, const char *aNewPath) { AssertReturnVoid (aOldPath); AssertReturnVoid (aNewPath); AutoLock alock (this); AssertReturnVoid (isReady()); size_t oldPathLen = strlen (aOldPath); Utf8Str path = mFilePathFull; LogFlowThisFunc (("VDI.fullPath={%s}\n", path.raw())); if (RTPathStartsWith (path, aOldPath)) { Utf8Str newPath = Utf8StrFmt ("%s%s", aNewPath, path.raw() + oldPathLen); path = newPath; mVirtualBox->calculateRelativePath (path, path); unconst (mFilePathFull) = newPath; unconst (mFilePath) = path; LogFlowThisFunc (("-> updated: full={%s} short={%s}\n", newPath.raw(), path.raw())); } } /** * Returns the string representation of this hard disk. * When \a aShort is false, returns the full image file path. * Otherwise, returns the image file name only. * * @param aShort if true, a short representation is returned * * @note Locks this object for reading. */ Bstr HVirtualDiskImage::toString (bool aShort /* = false */) { AutoReaderLock alock (this); if (!aShort) return mFilePathFull; else { Utf8Str fname = mFilePathFull; return RTPathFilename (fname.mutableRaw()); } } /** * Creates a clone of this hard disk by storing hard disk data in the given * VDI file. * * If the operation fails, @a aDeleteTarget will be set to @c true unless the * failure happened because the target file already existed. * * @param aId UUID to assign to the created image. * @param aTargetPath VDI file where the cloned image is to be to stored. * @param aProgress progress object to run during operation. * @param aDeleteTarget Whether it is recommended to delete target on * failure or not. */ HRESULT HVirtualDiskImage::cloneToImage (const Guid &aId, const Utf8Str &aTargetPath, Progress *aProgress, bool &aDeleteTarget) { /* normally, the target file should be deleted on error */ aDeleteTarget = true; AssertReturn (!aId.isEmpty(), E_FAIL); AssertReturn (!aTargetPath.isNull(), E_FAIL); AssertReturn (aProgress, E_FAIL); AutoLock alock (this); AssertReturn (isReady(), E_FAIL); AssertReturn (isBusy() == false, E_FAIL); /// @todo (dmik) cloning of differencing images is not yet supported AssertReturn (mParent.isNull(), E_FAIL); Utf8Str filePathFull = mFilePathFull; if (mState == NotCreated) return setError (E_FAIL, tr ("Source hard disk image '%s' is not yet created"), filePathFull.raw()); addReader(); alock.leave(); int vrc = VDICopyImage (aTargetPath, filePathFull, NULL, progressCallback, static_cast (aProgress)); alock.enter(); releaseReader(); /* We don't want to delete existing user files */ if (vrc == VERR_ALREADY_EXISTS) aDeleteTarget = false; if (VBOX_SUCCESS (vrc)) vrc = VDISetImageUUIDs (aTargetPath, aId, NULL, NULL, NULL); if (VBOX_FAILURE (vrc)) return setError (E_FAIL, tr ("Could not copy the hard disk image '%s' to '%s' (%Vrc)"), filePathFull.raw(), aTargetPath.raw(), vrc); return S_OK; } /** * Creates a new differencing image for this hard disk with the given * VDI file name. * * @param aId UUID to assign to the created image * @param aTargetPath VDI file where to store the created differencing image * @param aProgress progress object to run during operation * (can be NULL) */ HRESULT HVirtualDiskImage::createDiffImage (const Guid &aId, const Utf8Str &aTargetPath, Progress *aProgress) { AssertReturn (!aId.isEmpty(), E_FAIL); AssertReturn (!aTargetPath.isNull(), E_FAIL); AutoLock alock (this); AssertReturn (isReady(), E_FAIL); AssertReturn (isBusy() == false, E_FAIL); AssertReturn (mState >= Created, E_FAIL); addReader(); alock.leave(); int vrc = VDICreateDifferenceImage (aTargetPath, Utf8Str (mFilePathFull), NULL, aProgress ? progressCallback : NULL, static_cast (aProgress)); alock.enter(); releaseReader(); /* update the UUID to correspond to the file name */ if (VBOX_SUCCESS (vrc)) vrc = VDISetImageUUIDs (aTargetPath, aId, NULL, NULL, NULL); if (VBOX_FAILURE (vrc)) return setError (E_FAIL, tr ("Could not create a differencing hard disk '%s' (%Vrc)"), aTargetPath.raw(), vrc); return S_OK; } /** * Copies the image file of this hard disk to a separate VDI file (with an * unique creation UUID) and creates a new hard disk object for the copied * image. The copy will be created as a child of this hard disk's parent * (so that this hard disk must be a differencing one). * * The specified progress object (if not NULL) receives the percentage * of the operation completion. However, it is responsibility of the caller to * call Progress::notifyComplete() after this method returns. * * @param aFolder folder where to create a copy (must be a full path) * @param aMachineId machine ID the new hard disk will belong to * @param aHardDisk resulting hard disk object * @param aProgress progress object to run during copy operation * (may be NULL) * * @note * Must be NOT called from under locks of other objects that need external * access dirung this method execurion! */ HRESULT HVirtualDiskImage::cloneDiffImage (const Bstr &aFolder, const Guid &aMachineId, ComObjPtr &aHardDisk, Progress *aProgress) { AssertReturn (!aFolder.isEmpty() && !aMachineId.isEmpty(), E_FAIL); AutoLock alock (this); CHECK_READY(); AssertReturn (!mParent.isNull(), E_FAIL); ComAssertRet (isBusy() == false, E_FAIL); ComAssertRet (mState >= Created, E_FAIL); Guid id; id.create(); Utf8Str filePathTo = Utf8StrFmt ("%ls%c{%Vuuid}.vdi", aFolder.raw(), RTPATH_DELIMITER, id.ptr()); /* try to make the path relative to the vbox home dir */ const char *filePathToRel = filePathTo; { const Utf8Str &homeDir = mVirtualBox->homeDir(); if (!strncmp (filePathTo, homeDir, homeDir.length())) filePathToRel = (filePathToRel + homeDir.length() + 1); } /* first ensure the directory exists */ { Utf8Str dir = aFolder; if (!RTDirExists (dir)) { int vrc = RTDirCreateFullPath (dir, 0777); if (VBOX_FAILURE (vrc)) { return setError (E_FAIL, tr ("Could not create a directory '%s' " "to store the image file (%Vrc)"), dir.raw(), vrc); } } } Utf8Str filePathFull = mFilePathFull; alock.leave(); int vrc = VDICopyImage (filePathTo, filePathFull, NULL, progressCallback, static_cast (aProgress)); alock.enter(); /* get modification and parent UUIDs of this image */ RTUUID modUuid, parentUuid, parentModUuid; if (VBOX_SUCCESS (vrc)) vrc = VDIGetImageUUIDs (filePathFull, NULL, &modUuid, &parentUuid, &parentModUuid); // update the UUID of the copy to correspond to the file name // and copy all other UUIDs from this image if (VBOX_SUCCESS (vrc)) vrc = VDISetImageUUIDs (filePathTo, id, &modUuid, &parentUuid, &parentModUuid); if (VBOX_FAILURE (vrc)) return setError (E_FAIL, tr ("Could not copy the hard disk image '%s' to '%s' (%Vrc)"), filePathFull.raw(), filePathTo.raw(), vrc); ComObjPtr vdi; vdi.createObject(); HRESULT rc = vdi->init (mVirtualBox, mParent, Bstr (filePathToRel), TRUE /* aRegistered */); if (FAILED (rc)) return rc; /* associate the created hard disk with the given machine */ vdi->setMachineId (aMachineId); rc = mVirtualBox->registerHardDisk (vdi, VirtualBox::RHD_Internal); if (FAILED (rc)) return rc; aHardDisk = vdi; return S_OK; } /** * Merges this child image to its parent image and updates the parent UUID * of all children of this image (to point to this image's parent). * It's a responsibility of the caller to unregister and uninitialize * the merged image on success. * * This method is intended to be called on a worker thread (the operation * can be time consuming). * * The specified progress object (if not NULL) receives the percentage * of the operation completion. However, it is responsibility of the caller to * call Progress::notifyComplete() after this method returns. * * @param aProgress progress object to run during copy operation * (may be NULL) * * @note * This method expects that both this hard disk and the paret hard disk * are marked as busy using #setBusyWithChildren() prior to calling it! * Busy flags of both hard disks will be cleared by this method * on a successful return. In case of failure, #clearBusyWithChildren() * must be called on a parent. * * @note * Must be NOT called from under locks of other objects that need external * access dirung this method execurion! */ HRESULT HVirtualDiskImage::mergeImageToParent (Progress *aProgress) { LogFlowThisFunc (("mFilePathFull='%ls'\n", mFilePathFull.raw())); AutoLock alock (this); CHECK_READY(); AssertReturn (!mParent.isNull(), E_FAIL); AutoLock parentLock (mParent); ComAssertRet (isBusy() == true, E_FAIL); ComAssertRet (mParent->isBusy() == true, E_FAIL); ComAssertRet (mState >= Created && mParent->asVDI()->mState >= Created, E_FAIL); ComAssertMsgRet (mParent->storageType() == HardDiskStorageType_VirtualDiskImage, ("non VDI storage types are not yet supported!"), E_FAIL); parentLock.leave(); alock.leave(); int vrc = VDIMergeImage (Utf8Str (mFilePathFull), Utf8Str (mParent->asVDI()->mFilePathFull), progressCallback, static_cast (aProgress)); alock.enter(); parentLock.enter(); if (VBOX_FAILURE (vrc)) return setError (E_FAIL, tr ("Could not merge the hard disk image '%ls' to " "its parent image '%ls' (%Vrc)"), mFilePathFull.raw(), mParent->asVDI()->mFilePathFull.raw(), vrc); { HRESULT rc = S_OK; AutoReaderLock chLock (childrenLock()); for (HardDiskList::const_iterator it = children().begin(); it != children().end(); ++ it) { ComObjPtr child = (*it)->asVDI(); AutoLock childLock (child); /* reparent the child */ child->mParent = mParent; if (mParent) mParent->addDependentChild (child); /* change the parent UUID in the image as well */ RTUUID parentUuid, parentModUuid; vrc = VDIGetImageUUIDs (Utf8Str (mParent->asVDI()->mFilePathFull), &parentUuid, &parentModUuid, NULL, NULL); if (VBOX_FAILURE (vrc)) { rc = setError (E_FAIL, tr ("Could not access the hard disk image '%ls' (%Vrc)"), mParent->asVDI()->mFilePathFull.raw(), vrc); break; } ComAssertBreak (mParent->id() == Guid (parentUuid), rc = E_FAIL); vrc = VDISetImageUUIDs (Utf8Str (child->mFilePathFull), NULL, NULL, &parentUuid, &parentModUuid); if (VBOX_FAILURE (vrc)) { rc = setError (E_FAIL, tr ("Could not update parent UUID of the hard disk image " "'%ls' (%Vrc)"), child->mFilePathFull.raw(), vrc); break; } } if (FAILED (rc)) return rc; } /* detach all our children to avoid their uninit in #uninit() */ removeDependentChildren(); mParent->clearBusy(); clearBusy(); return S_OK; } /** * Merges this image to all its child images, updates the parent UUID * of all children of this image (to point to this image's parent). * It's a responsibility of the caller to unregister and uninitialize * the merged image on success. * * This method is intended to be called on a worker thread (the operation * can be time consuming). * * The specified progress object (if not NULL) receives the percentage * of the operation completion. However, it is responsibility of the caller to * call Progress::notifyComplete() after this method returns. * * @param aProgress progress object to run during copy operation * (may be NULL) * * @note * This method expects that both this hard disk and all children * are marked as busy using setBusyWithChildren() prior to calling it! * Busy flags of all affected hard disks will be cleared by this method * on a successful return. In case of failure, #clearBusyWithChildren() * must be called for this hard disk. * * @note * Must be NOT called from under locks of other objects that need external * access dirung this method execurion! */ HRESULT HVirtualDiskImage::mergeImageToChildren (Progress *aProgress) { LogFlowThisFunc (("mFilePathFull='%ls'\n", mFilePathFull.raw())); AutoLock alock (this); CHECK_READY(); /* this must be a diff image */ AssertReturn (isDifferencing(), E_FAIL); ComAssertRet (isBusy() == true, E_FAIL); ComAssertRet (mState >= Created, E_FAIL); ComAssertMsgRet (mParent->storageType() == HardDiskStorageType_VirtualDiskImage, ("non VDI storage types are not yet supported!"), E_FAIL); { HRESULT rc = S_OK; AutoLock chLock (childrenLock()); /* iterate over a copy since we will modify the list */ HardDiskList list = children(); for (HardDiskList::const_iterator it = list.begin(); it != list.end(); ++ it) { ComObjPtr hd = *it; ComObjPtr child = hd->asVDI(); AutoLock childLock (child); ComAssertRet (child->isBusy() == true, E_FAIL); ComAssertBreak (child->mState >= Created, rc = E_FAIL); childLock.leave(); alock.leave(); int vrc = VDIMergeImage (Utf8Str (mFilePathFull), Utf8Str (child->mFilePathFull), progressCallback, static_cast (aProgress)); alock.enter(); childLock.enter(); if (VBOX_FAILURE (vrc)) { rc = setError (E_FAIL, tr ("Could not merge the hard disk image '%ls' to " "its parent image '%ls' (%Vrc)"), mFilePathFull.raw(), child->mFilePathFull.raw(), vrc); break; } /* reparent the child */ child->mParent = mParent; if (mParent) mParent->addDependentChild (child); /* change the parent UUID in the image as well */ RTUUID parentUuid, parentModUuid; vrc = VDIGetImageUUIDs (Utf8Str (mParent->asVDI()->mFilePathFull), &parentUuid, &parentModUuid, NULL, NULL); if (VBOX_FAILURE (vrc)) { rc = setError (E_FAIL, tr ("Could not access the hard disk image '%ls' (%Vrc)"), mParent->asVDI()->mFilePathFull.raw(), vrc); break; } ComAssertBreak (mParent->id() == Guid (parentUuid), rc = E_FAIL); vrc = VDISetImageUUIDs (Utf8Str (child->mFilePathFull), NULL, NULL, &parentUuid, &parentModUuid); if (VBOX_FAILURE (vrc)) { rc = setError (E_FAIL, tr ("Could not update parent UUID of the hard disk image " "'%ls' (%Vrc)"), child->mFilePathFull.raw(), vrc); break; } /* detach child to avoid its uninit in #uninit() */ removeDependentChild (child); /* remove the busy flag */ child->clearBusy(); } if (FAILED (rc)) return rc; } clearBusy(); return S_OK; } /** * Deletes and recreates the differencing hard disk image from scratch. * The file name and UUID remain the same. */ HRESULT HVirtualDiskImage::wipeOutImage() { AutoLock alock (this); CHECK_READY(); AssertReturn (isDifferencing(), E_FAIL); AssertReturn (mRegistered, E_FAIL); AssertReturn (mState >= Created, E_FAIL); ComAssertMsgRet (mParent->storageType() == HardDiskStorageType_VirtualDiskImage, ("non-VDI storage types are not yet supported!"), E_FAIL); Utf8Str filePathFull = mFilePathFull; int vrc = RTFileDelete (filePathFull); if (VBOX_FAILURE (vrc)) return setError (E_FAIL, tr ("Could not delete the image file '%s' (%Vrc)"), filePathFull.raw(), vrc); vrc = VDICreateDifferenceImage (filePathFull, Utf8Str (mParent->asVDI()->mFilePathFull), NULL, NULL, NULL); /* update the UUID to correspond to the file name */ if (VBOX_SUCCESS (vrc)) vrc = VDISetImageUUIDs (filePathFull, mId, NULL, NULL, NULL); if (VBOX_FAILURE (vrc)) return setError (E_FAIL, tr ("Could not create a differencing hard disk '%s' (%Vrc)"), filePathFull.raw(), vrc); return S_OK; } HRESULT HVirtualDiskImage::deleteImage (bool aIgnoreErrors /* = false */) { AutoLock alock (this); CHECK_READY(); AssertReturn (!mRegistered, E_FAIL); AssertReturn (mState >= Created, E_FAIL); int vrc = RTFileDelete (Utf8Str (mFilePathFull)); if (VBOX_FAILURE (vrc) && !aIgnoreErrors) return setError (E_FAIL, tr ("Could not delete the image file '%ls' (%Vrc)"), mFilePathFull.raw(), vrc); return S_OK; } // private methods ///////////////////////////////////////////////////////////////////////////// /** * Helper to set a new file path. * Resolves a path relatively to the Virtual Box home directory. * * @note * Must be called from under the object's lock! */ HRESULT HVirtualDiskImage::setFilePath (const BSTR aFilePath) { if (aFilePath && *aFilePath) { /* get the full file name */ char filePathFull [RTPATH_MAX]; int vrc = RTPathAbsEx (mVirtualBox->homeDir(), Utf8Str (aFilePath), filePathFull, sizeof (filePathFull)); if (VBOX_FAILURE (vrc)) return setError (E_FAIL, tr ("Invalid image file path '%ls' (%Vrc)"), aFilePath, vrc); mFilePath = aFilePath; mFilePathFull = filePathFull; } else { mFilePath.setNull(); mFilePathFull.setNull(); } return S_OK; } /** * Helper to query information about the VDI hard disk. * * @param aAccessError not used when NULL, otherwise see #getAccessible() * * @note Must be called from under the object's write lock, only after * CHECK_BUSY_AND_READERS() succeeds. */ HRESULT HVirtualDiskImage::queryInformation (Bstr *aAccessError) { AssertReturn (isLockedOnCurrentThread(), E_FAIL); /* create a lock object to completely release it later */ AutoLock alock (this); AssertReturn (mStateCheckWaiters == 0, E_FAIL); ComAssertRet (mState >= Created, E_FAIL); HRESULT rc = S_OK; int vrc = VINF_SUCCESS; /* lazily create a semaphore */ vrc = RTSemEventMultiCreate (&mStateCheckSem); ComAssertRCRet (vrc, E_FAIL); /* Reference the disk to prevent any concurrent modifications * after releasing the lock below (to unblock getters before * a lengthy operation). */ addReader(); alock.leave(); /* VBoxVHDD management interface needs to be optimized: we're opening a * file three times in a raw to get three bits of information. */ Utf8Str filePath = mFilePathFull; Bstr errMsg; do { /* check the image file */ Guid id, parentId; vrc = VDICheckImage (filePath, NULL, NULL, NULL, id.ptr(), parentId.ptr(), NULL, 0); if (VBOX_FAILURE (vrc)) break; if (!mId.isEmpty()) { /* check that the actual UUID of the image matches the stored UUID */ if (mId != id) { errMsg = Utf8StrFmt ( tr ("Actual UUID {%Vuuid} of the hard disk image '%s' doesn't " "match UUID {%Vuuid} stored in the registry"), id.ptr(), filePath.raw(), mId.ptr()); break; } } else { /* assgn an UUID read from the image file */ mId = id; } if (mParent) { /* check parent UUID */ AutoLock parentLock (mParent); if (mParent->id() != parentId) { errMsg = Utf8StrFmt ( tr ("UUID {%Vuuid} of the parent image '%ls' stored in " "the hard disk image file '%s' doesn't match " "UUID {%Vuuid} stored in the registry"), parentId.raw(), mParent->toString().raw(), filePath.raw(), mParent->id().raw()); break; } } else if (!parentId.isEmpty()) { errMsg = Utf8StrFmt ( tr ("Hard disk image '%s' is a differencing image that is linked " "to a hard disk with UUID {%Vuuid} and cannot be used " "directly as a base hard disk"), filePath.raw(), parentId.raw()); break; } { RTFILE file = NIL_RTFILE; vrc = RTFileOpen (&file, filePath, RTFILE_O_READ); if (VBOX_SUCCESS (vrc)) { uint64_t size = 0; vrc = RTFileGetSize (file, &size); if (VBOX_SUCCESS (vrc)) mActualSize = size; RTFileClose (file); } if (VBOX_FAILURE (vrc)) break; } if (!mParent) { /* query logical size only for non-differencing images */ PVDIDISK disk = VDIDiskCreate(); vrc = VDIDiskOpenImage (disk, Utf8Str (mFilePathFull), VDI_OPEN_FLAGS_READONLY); if (VBOX_SUCCESS (vrc)) { uint64_t size = VDIDiskGetSize (disk); /* convert to MBytes */ mSize = size / 1024 / 1024; } VDIDiskDestroy (disk); if (VBOX_FAILURE (vrc)) break; } } while (0); /* enter the lock again */ alock.enter(); /* remove the reference */ releaseReader(); if (FAILED (rc) || VBOX_FAILURE (vrc) || !errMsg.isNull()) { LogWarningFunc (("'%ls' is not accessible " "(rc=%08X, vrc=%Vrc, errMsg='%ls')\n", mFilePathFull.raw(), rc, vrc, errMsg.raw())); if (aAccessError) { if (!errMsg.isNull()) *aAccessError = errMsg; else if (VBOX_FAILURE (vrc)) *aAccessError = Utf8StrFmt ( tr ("Could not access hard disk image '%ls' (%Vrc)"), mFilePathFull.raw(), vrc); } /* downgrade to not accessible */ mState = Created; } else { if (aAccessError) aAccessError->setNull(); mState = Accessible; } /* inform waiters if there are any */ if (mStateCheckWaiters > 0) { RTSemEventMultiSignal (mStateCheckSem); } else { /* delete the semaphore ourselves */ RTSemEventMultiDestroy (mStateCheckSem); mStateCheckSem = NIL_RTSEMEVENTMULTI; } return rc; } /** * Helper to create hard disk images. * * @param aSize size in MB * @param aDynamic dynamic or fixed image * @param aProgress address of IProgress pointer to return */ HRESULT HVirtualDiskImage::createImage (ULONG64 aSize, BOOL aDynamic, IProgress **aProgress) { AutoLock alock (this); CHECK_BUSY_AND_READERS(); if (mState != NotCreated) return setError (E_ACCESSDENIED, tr ("Hard disk image '%ls' is already created"), mFilePathFull.raw()); if (!mFilePathFull) return setError (E_ACCESSDENIED, tr ("Cannot create a hard disk image using an empty (null) file path"), mFilePathFull.raw()); /* first ensure the directory exists */ { Utf8Str imageDir = mFilePathFull; RTPathStripFilename (imageDir.mutableRaw()); if (!RTDirExists (imageDir)) { int vrc = RTDirCreateFullPath (imageDir, 0777); if (VBOX_FAILURE (vrc)) { return setError (E_FAIL, tr ("Could not create a directory '%s' " "to store the image file (%Vrc)"), imageDir.raw(), vrc); } } } /* check whether the given file exists or not */ RTFILE file; int vrc = RTFileOpen (&file, Utf8Str (mFilePathFull), RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE); if (vrc != VERR_FILE_NOT_FOUND) { if (VBOX_SUCCESS (vrc)) RTFileClose (file); switch(vrc) { case VINF_SUCCESS: return setError (E_FAIL, tr ("Image file '%ls' already exists"), mFilePathFull.raw()); default: return setError(E_FAIL, tr ("Invalid image file path '%ls' (%Vrc)"), mFilePathFull.raw(), vrc); } } /* check VDI size limits */ { HRESULT rc; ULONG64 maxVDISize; ComPtr props; rc = mVirtualBox->COMGETTER(SystemProperties) (props.asOutParam()); ComAssertComRCRet (rc, E_FAIL); rc = props->COMGETTER(MaxVDISize) (&maxVDISize); ComAssertComRCRet (rc, E_FAIL); if (aSize < 1 || aSize > maxVDISize) return setError (E_INVALIDARG, tr ("Invalid VDI size: %llu MB (must be in range [1, %llu] MB)"), aSize, maxVDISize); } HRESULT rc; /* create a project object */ ComObjPtr progress; progress.createObject(); { Bstr desc = aDynamic ? tr ("Creating a dynamically expanding hard disk") : tr ("Creating a fixed-size hard disk"); rc = progress->init (mVirtualBox, static_cast (this), desc, FALSE /* aCancelable */); CheckComRCReturnRC (rc); } /* mark as busy (being created) * (VDI task thread will unmark it) */ setBusy(); /* fill in VDI task data */ VDITask *task = new VDITask (aDynamic ? VDITask::CreateDynamic : VDITask::CreateStatic, this, progress); task->size = aSize; task->size *= 1024 * 1024; /* convert to bytes */ /* create the hard disk creation thread, pass operation data */ vrc = RTThreadCreate (NULL, VDITaskThread, (void *) task, 0, RTTHREADTYPE_MAIN_HEAVY_WORKER, 0, "VDITask"); ComAssertMsgRC (vrc, ("Could not create a thread (%Vrc)", vrc)); if (VBOX_FAILURE (vrc)) { clearBusy(); delete task; rc = E_FAIL; } else { /* get one interface for the caller */ progress.queryInterfaceTo (aProgress); } return rc; } /* static */ DECLCALLBACK(int) HVirtualDiskImage::VDITaskThread (RTTHREAD thread, void *pvUser) { VDITask *task = static_cast (pvUser); AssertReturn (task, VERR_GENERAL_FAILURE); LogFlowFunc (("operation=%d, size=%llu\n", task->operation, task->size)); VDIIMAGETYPE type = (VDIIMAGETYPE) 0; switch (task->operation) { case VDITask::CreateDynamic: type = VDI_IMAGE_TYPE_NORMAL; break; case VDITask::CreateStatic: type = VDI_IMAGE_TYPE_FIXED; break; case VDITask::CloneToImage: break; default: AssertFailedReturn (VERR_GENERAL_FAILURE); break; } HRESULT rc = S_OK; Utf8Str errorMsg; bool deleteTarget = true; if (task->operation == VDITask::CloneToImage) { Assert (!task->vdi->id().isEmpty()); /// @todo (dmik) check locks AutoLock sourceLock (task->source); rc = task->source->cloneToImage (task->vdi->id(), Utf8Str (task->vdi->filePathFull()), task->progress, deleteTarget); /* release reader added in HardDisk::CloneToImage() */ task->source->releaseReader(); } else { int vrc = VDICreateBaseImage (Utf8Str (task->vdi->filePathFull()), type, task->size, Utf8Str (task->vdi->mDescription), progressCallback, static_cast (task->progress)); /* We don't want to delete existing user files */ if (vrc == VERR_ALREADY_EXISTS) deleteTarget = false; if (VBOX_SUCCESS (vrc) && task->vdi->id()) { /* we have a non-null UUID, update the created image */ vrc = VDISetImageUUIDs (Utf8Str (task->vdi->filePathFull()), task->vdi->id().raw(), NULL, NULL, NULL); } if (VBOX_FAILURE (vrc)) { errorMsg = Utf8StrFmt ( tr ("Falied to create a hard disk image '%ls' (%Vrc)"), task->vdi->filePathFull().raw(), vrc); rc = E_FAIL; } } LogFlowFunc (("rc=%08X\n", rc)); AutoLock alock (task->vdi); /* clear busy set in in HardDisk::CloneToImage() or * in HVirtualDiskImage::createImage() */ task->vdi->clearBusy(); if (SUCCEEDED (rc)) { task->vdi->mState = HVirtualDiskImage::Created; /* update VDI data fields */ Bstr errMsg; rc = task->vdi->queryInformation (&errMsg); /* we want to deliver the access check result to the caller * immediately, before he calls HardDisk::GetAccssible() himself. */ if (SUCCEEDED (rc) && !errMsg.isNull()) task->progress->notifyCompleteBstr ( E_FAIL, COM_IIDOF (IVirtualDiskImage), getComponentName(), errMsg); else task->progress->notifyComplete (rc); } else { /* delete the target file so we don't have orphaned files */ if (deleteTarget) RTFileDelete(Utf8Str (task->vdi->filePathFull())); task->vdi->mState = HVirtualDiskImage::NotCreated; /* complete the progress object */ if (errorMsg) task->progress->notifyComplete ( E_FAIL, COM_IIDOF (IVirtualDiskImage), getComponentName(), errorMsg); else task->progress->notifyComplete (rc); } delete task; return VINF_SUCCESS; } //////////////////////////////////////////////////////////////////////////////// // HISCSIHardDisk class //////////////////////////////////////////////////////////////////////////////// // constructor / destructor //////////////////////////////////////////////////////////////////////////////// HRESULT HISCSIHardDisk::FinalConstruct() { HRESULT rc = HardDisk::FinalConstruct(); if (FAILED (rc)) return rc; mSize = 0; mActualSize = 0; mPort = 0; mLun = 0; return S_OK; } void HISCSIHardDisk::FinalRelease() { HardDisk::FinalRelease(); } // public initializer/uninitializer for internal purposes only //////////////////////////////////////////////////////////////////////////////// // public methods for internal purposes only ///////////////////////////////////////////////////////////////////////////// /** * Initializes the iSCSI hard disk object by reading its properties from * the given configuration node. The created hard disk will be marked as * registered on success. * * @param aHDNode node. * @param aVDINod node. */ HRESULT HISCSIHardDisk::init (VirtualBox *aVirtualBox, const settings::Key &aHDNode, const settings::Key &aISCSINode) { using namespace settings; LogFlowThisFunc (("\n")); AssertReturn (!aHDNode.isNull() && !aISCSINode.isNull(), E_FAIL); AutoLock alock (this); ComAssertRet (!isReady(), E_UNEXPECTED); mStorageType = HardDiskStorageType_ISCSIHardDisk; HRESULT rc = S_OK; do { rc = protectedInit (aVirtualBox, NULL); CheckComRCBreakRC (rc); /* set ready to let protectedUninit() be called on failure */ setReady (true); /* server (required) */ mServer = aISCSINode.stringValue ("server"); /* target (required) */ mTarget = aISCSINode.stringValue ("target"); /* port (optional) */ mPort = aISCSINode.value ("port"); /* lun (optional) */ mLun = aISCSINode.value ("lun"); /* userName (optional) */ mUserName = aISCSINode.stringValue ("userName"); /* password (optional) */ mPassword = aISCSINode.stringValue ("password"); LogFlowThisFunc (("'iscsi:%ls:%hu@%ls/%ls:%llu'\n", mServer.raw(), mPort, mUserName.raw(), mTarget.raw(), mLun)); /* load basic settings and children */ rc = loadSettings (aHDNode); CheckComRCBreakRC (rc); if (mType != HardDiskType_Writethrough) { rc = setError (E_FAIL, tr ("Currently, non-Writethrough iSCSI hard disks are not " "allowed ('%ls')"), toString().raw()); break; } mRegistered = TRUE; } while (0); if (FAILED (rc)) uninit(); return rc; } /** * Initializes the iSCSI hard disk object using default values for all * properties. The created hard disk will NOT be marked as registered. */ HRESULT HISCSIHardDisk::init (VirtualBox *aVirtualBox) { LogFlowThisFunc (("\n")); AutoLock alock (this); ComAssertRet (!isReady(), E_UNEXPECTED); mStorageType = HardDiskStorageType_ISCSIHardDisk; HRESULT rc = S_OK; do { rc = protectedInit (aVirtualBox, NULL); CheckComRCBreakRC (rc); /* set ready to let protectedUninit() be called on failure */ setReady (true); /* we have to generate a new UUID */ mId.create(); /* currently, all iSCSI hard disks are writethrough */ mType = HardDiskType_Writethrough; mRegistered = FALSE; } while (0); if (FAILED (rc)) uninit(); return rc; } /** * Uninitializes the instance and sets the ready flag to FALSE. * Called either from FinalRelease(), by the parent when it gets destroyed, * or by a third party when it decides this object is no more valid. */ void HISCSIHardDisk::uninit() { LogFlowThisFunc (("\n")); AutoLock alock (this); if (!isReady()) return; HardDisk::protectedUninit (alock); } // IHardDisk properties //////////////////////////////////////////////////////////////////////////////// STDMETHODIMP HISCSIHardDisk::COMGETTER(Description) (BSTR *aDescription) { if (!aDescription) return E_POINTER; AutoReaderLock alock (this); CHECK_READY(); mDescription.cloneTo (aDescription); return S_OK; } STDMETHODIMP HISCSIHardDisk::COMSETTER(Description) (INPTR BSTR aDescription) { AutoLock alock (this); CHECK_READY(); CHECK_BUSY_AND_READERS(); if (mDescription != aDescription) { mDescription = aDescription; if (mRegistered) return mVirtualBox->saveSettings(); } return S_OK; } STDMETHODIMP HISCSIHardDisk::COMGETTER(Size) (ULONG64 *aSize) { if (!aSize) return E_POINTER; AutoReaderLock alock (this); CHECK_READY(); *aSize = mSize; return S_OK; } STDMETHODIMP HISCSIHardDisk::COMGETTER(ActualSize) (ULONG64 *aActualSize) { if (!aActualSize) return E_POINTER; AutoReaderLock alock (this); CHECK_READY(); *aActualSize = mActualSize; return S_OK; } // IISCSIHardDisk properties //////////////////////////////////////////////////////////////////////////////// STDMETHODIMP HISCSIHardDisk::COMGETTER(Server) (BSTR *aServer) { if (!aServer) return E_POINTER; AutoReaderLock alock (this); CHECK_READY(); mServer.cloneTo (aServer); return S_OK; } STDMETHODIMP HISCSIHardDisk::COMSETTER(Server) (INPTR BSTR aServer) { if (!aServer || !*aServer) return E_INVALIDARG; AutoLock alock (this); CHECK_READY(); CHECK_BUSY_AND_READERS(); if (mServer != aServer) { mServer = aServer; if (mRegistered) return mVirtualBox->saveSettings(); } return S_OK; } STDMETHODIMP HISCSIHardDisk::COMGETTER(Port) (USHORT *aPort) { if (!aPort) return E_POINTER; AutoReaderLock alock (this); CHECK_READY(); *aPort = mPort; return S_OK; } STDMETHODIMP HISCSIHardDisk::COMSETTER(Port) (USHORT aPort) { AutoLock alock (this); CHECK_READY(); CHECK_BUSY_AND_READERS(); if (mPort != aPort) { mPort = aPort; if (mRegistered) return mVirtualBox->saveSettings(); } return S_OK; } STDMETHODIMP HISCSIHardDisk::COMGETTER(Target) (BSTR *aTarget) { if (!aTarget) return E_POINTER; AutoReaderLock alock (this); CHECK_READY(); mTarget.cloneTo (aTarget); return S_OK; } STDMETHODIMP HISCSIHardDisk::COMSETTER(Target) (INPTR BSTR aTarget) { if (!aTarget || !*aTarget) return E_INVALIDARG; AutoLock alock (this); CHECK_READY(); CHECK_BUSY_AND_READERS(); if (mTarget != aTarget) { mTarget = aTarget; if (mRegistered) return mVirtualBox->saveSettings(); } return S_OK; } STDMETHODIMP HISCSIHardDisk::COMGETTER(Lun) (ULONG64 *aLun) { if (!aLun) return E_POINTER; AutoReaderLock alock (this); CHECK_READY(); *aLun = mLun; return S_OK; } STDMETHODIMP HISCSIHardDisk::COMSETTER(Lun) (ULONG64 aLun) { AutoLock alock (this); CHECK_READY(); CHECK_BUSY_AND_READERS(); if (mLun != aLun) { mLun = aLun; if (mRegistered) return mVirtualBox->saveSettings(); } return S_OK; } STDMETHODIMP HISCSIHardDisk::COMGETTER(UserName) (BSTR *aUserName) { if (!aUserName) return E_POINTER; AutoReaderLock alock (this); CHECK_READY(); mUserName.cloneTo (aUserName); return S_OK; } STDMETHODIMP HISCSIHardDisk::COMSETTER(UserName) (INPTR BSTR aUserName) { AutoLock alock (this); CHECK_READY(); CHECK_BUSY_AND_READERS(); if (mUserName != aUserName) { mUserName = aUserName; if (mRegistered) return mVirtualBox->saveSettings(); } return S_OK; } STDMETHODIMP HISCSIHardDisk::COMGETTER(Password) (BSTR *aPassword) { if (!aPassword) return E_POINTER; AutoReaderLock alock (this); CHECK_READY(); mPassword.cloneTo (aPassword); return S_OK; } STDMETHODIMP HISCSIHardDisk::COMSETTER(Password) (INPTR BSTR aPassword) { AutoLock alock (this); CHECK_READY(); CHECK_BUSY_AND_READERS(); if (mPassword != aPassword) { mPassword = aPassword; if (mRegistered) return mVirtualBox->saveSettings(); } return S_OK; } // public/protected methods for internal purposes only ///////////////////////////////////////////////////////////////////////////// /** * Attempts to mark the hard disk as registered. * Only VirtualBox can call this method. */ HRESULT HISCSIHardDisk::trySetRegistered (BOOL aRegistered) { AutoLock alock (this); CHECK_READY(); if (aRegistered) { if (mServer.isEmpty() || mTarget.isEmpty()) return setError (E_FAIL, tr ("iSCSI Hard disk has no server or target defined")); } else { } return HardDisk::trySetRegistered (aRegistered); } /** * Checks accessibility of this iSCSI hard disk. * * @note Locks this object for writing. */ HRESULT HISCSIHardDisk::getAccessible (Bstr &aAccessError) { /* queryInformation() needs a write lock */ AutoLock alock (this); CHECK_READY(); /* check the basic accessibility */ HRESULT rc = getBaseAccessible (aAccessError); if (FAILED (rc) || !aAccessError.isNull()) return rc; return queryInformation (aAccessError); } /** * Saves hard disk settings to the specified storage node and saves * all children to the specified hard disk node * * @param aHDNode . * @param aStorageNode node. */ HRESULT HISCSIHardDisk::saveSettings (settings::Key &aHDNode, settings::Key &aStorageNode) { AssertReturn (!aHDNode.isNull() && !aStorageNode.isNull(), E_FAIL); AutoReaderLock alock (this); CHECK_READY(); /* server (required) */ aStorageNode.setValue ("server", mServer); /* target (required) */ aStorageNode.setValue ("target", mTarget); /* port (optional, defaults to 0) */ aStorageNode.setValueOr ("port", mPort, 0); /* lun (optional, force 0x format to conform to XML Schema!) */ aStorageNode.setValueOr ("lun", mLun, 0, 16); /* userName (optional) */ aStorageNode.setValueOr ("userName", mUserName, Bstr::Null); /* password (optional) */ aStorageNode.setValueOr ("password", mPassword, Bstr::Null); /* save basic settings and children */ return HardDisk::saveSettings (aHDNode); } /** * Returns the string representation of this hard disk. * When \a aShort is false, returns the full image file path. * Otherwise, returns the image file name only. * * @param aShort if true, a short representation is returned * * @note Locks this object for reading. */ Bstr HISCSIHardDisk::toString (bool aShort /* = false */) { AutoReaderLock alock (this); Bstr str; if (!aShort) { /* the format is iscsi:[][:]/[:] */ str = Utf8StrFmt ("iscsi:%s%ls%s/%ls%s", !mUserName.isNull() ? Utf8StrFmt ("%ls@", mUserName.raw()).raw() : "", mServer.raw(), mPort != 0 ? Utf8StrFmt (":%hu", mPort).raw() : "", mTarget.raw(), mLun != 0 ? Utf8StrFmt (":%llu", mLun).raw() : ""); } else { str = Utf8StrFmt ("%ls%s", mTarget.raw(), mLun != 0 ? Utf8StrFmt (":%llu", mLun).raw() : ""); } return str; } /** * Creates a clone of this hard disk by storing hard disk data in the given * VDI file. * * If the operation fails, @a aDeleteTarget will be set to @c true unless the * failure happened because the target file already existed. * * @param aId UUID to assign to the created image. * @param aTargetPath VDI file where the cloned image is to be to stored. * @param aProgress progress object to run during operation. * @param aDeleteTarget Whether it is recommended to delete target on * failure or not. */ HRESULT HISCSIHardDisk::cloneToImage (const Guid &aId, const Utf8Str &aTargetPath, Progress *aProgress, bool &aDeleteTarget) { ComAssertMsgFailed (("Not implemented")); return E_NOTIMPL; // AssertReturn (isBusy() == false, E_FAIL); // addReader(); // releaseReader(); } /** * Creates a new differencing image for this hard disk with the given * VDI file name. * * @param aId UUID to assign to the created image * @param aTargetPath VDI file where to store the created differencing image * @param aProgress progress object to run during operation * (can be NULL) */ HRESULT HISCSIHardDisk::createDiffImage (const Guid &aId, const Utf8Str &aTargetPath, Progress *aProgress) { ComAssertMsgFailed (("Not implemented")); return E_NOTIMPL; // AssertReturn (isBusy() == false, E_FAIL); // addReader(); // releaseReader(); } // private methods ///////////////////////////////////////////////////////////////////////////// /** * Helper to query information about the iSCSI hard disk. * * @param aAccessError see #getAccessible() * * @note Must be called from under the object's write lock, only after * CHECK_BUSY_AND_READERS() succeeds. */ HRESULT HISCSIHardDisk::queryInformation (Bstr &aAccessError) { AssertReturn (isLockedOnCurrentThread(), E_FAIL); /* create a lock object to completely release it later */ AutoLock alock (this); /// @todo (dmik) query info about this iSCSI disk, // set mSize and mActualSize, // or set aAccessError in case of failure aAccessError.setNull(); return S_OK; } //////////////////////////////////////////////////////////////////////////////// // HVMDKImage class //////////////////////////////////////////////////////////////////////////////// // constructor / destructor //////////////////////////////////////////////////////////////////////////////// HRESULT HVMDKImage::FinalConstruct() { HRESULT rc = HardDisk::FinalConstruct(); if (FAILED (rc)) return rc; mState = NotCreated; mStateCheckSem = NIL_RTSEMEVENTMULTI; mStateCheckWaiters = 0; mSize = 0; mActualSize = 0; /* initialize the container */ int vrc = VDCreate (VDError, this, &mContainer); ComAssertRCRet (vrc, E_FAIL); return S_OK; } void HVMDKImage::FinalRelease() { if (mContainer != NULL) VDDestroy (mContainer); HardDisk::FinalRelease(); } // public initializer/uninitializer for internal purposes only //////////////////////////////////////////////////////////////////////////////// // public methods for internal purposes only ///////////////////////////////////////////////////////////////////////////// /** * Initializes the VMDK hard disk object by reading its properties from * the given configuration node. The created hard disk will be marked as * registered on success. * * @param aHDNode node. * @param aVMDKNode node. */ HRESULT HVMDKImage::init (VirtualBox *aVirtualBox, HardDisk *aParent, const settings::Key &aHDNode, const settings::Key &aVMDKNode) { using namespace settings; LogFlowThisFunc (("\n")); AssertReturn (!aHDNode.isNull() && !aVMDKNode.isNull(), E_FAIL); AutoLock alock (this); ComAssertRet (!isReady(), E_UNEXPECTED); mStorageType = HardDiskStorageType_VMDKImage; HRESULT rc = S_OK; do { rc = protectedInit (aVirtualBox, aParent); CheckComRCBreakRC (rc); /* set ready to let protectedUninit() be called on failure */ setReady (true); /* filePath (required) */ Bstr filePath = aVMDKNode.stringValue ("filePath"); rc = setFilePath (filePath); CheckComRCBreakRC (rc); LogFlowThisFunc (("'%ls'\n", mFilePathFull.raw())); /* load basic settings and children */ rc = loadSettings (aHDNode); CheckComRCBreakRC (rc); if (mType != HardDiskType_Writethrough) { rc = setError (E_FAIL, tr ("Currently, non-Writethrough VMDK images are not " "allowed ('%ls')"), toString().raw()); break; } mState = Created; mRegistered = TRUE; /* Don't call queryInformation() for registered hard disks to * prevent the calling thread (i.e. the VirtualBox server startup * thread) from an unexpected freeze. The vital mId property (UUID) * is read from the registry file in loadSettings(). To get the rest, * the user will have to call COMGETTER(Accessible) manually. */ } while (0); if (FAILED (rc)) uninit(); return rc; } /** * Initializes the VMDK hard disk object using the given image file name. * * @param aVirtualBox VirtualBox parent. * @param aParent Currently, must always be @c NULL. * @param aFilePath Path to the image file, or @c NULL to create an * image-less object. * @param aRegistered Whether to mark this disk as registered or not * (ignored when @a aFilePath is @c NULL, assuming @c FALSE) */ HRESULT HVMDKImage::init (VirtualBox *aVirtualBox, HardDisk *aParent, const BSTR aFilePath, BOOL aRegistered /* = FALSE */) { LogFlowThisFunc (("aFilePath='%ls', aRegistered=%d\n", aFilePath, aRegistered)); AssertReturn (aParent == NULL, E_FAIL); AutoLock alock (this); ComAssertRet (!isReady(), E_UNEXPECTED); mStorageType = HardDiskStorageType_VMDKImage; HRESULT rc = S_OK; do { rc = protectedInit (aVirtualBox, aParent); CheckComRCBreakRC (rc); /* set ready to let protectedUninit() be called on failure */ setReady (true); rc = setFilePath (aFilePath); CheckComRCBreakRC (rc); /* currently, all VMDK hard disks are writethrough */ mType = HardDiskType_Writethrough; Assert (mId.isEmpty()); if (aFilePath && *aFilePath) { mRegistered = aRegistered; mState = Created; /* Call queryInformation() anyway (even if it will block), because * it is the only way to get the UUID of the existing VDI and * initialize the vital mId property. */ Bstr errMsg; rc = queryInformation (&errMsg); if (SUCCEEDED (rc)) { /* We are constructing a new HVirtualDiskImage object. If there * is a fatal accessibility error (we cannot read image UUID), * we have to fail. We do so even on non-fatal errors as well, * because it's not worth to keep going with the inaccessible * image from the very beginning (when nothing else depends on * it yet). */ if (!errMsg.isNull()) rc = setErrorBstr (E_FAIL, errMsg); } } else { mRegistered = FALSE; mState = NotCreated; mId.create(); } } while (0); if (FAILED (rc)) uninit(); return rc; } /** * Uninitializes the instance and sets the ready flag to FALSE. * Called either from FinalRelease(), by the parent when it gets destroyed, * or by a third party when it decides this object is no more valid. */ void HVMDKImage::uninit() { LogFlowThisFunc (("\n")); AutoLock alock (this); if (!isReady()) return; HardDisk::protectedUninit (alock); } // IHardDisk properties //////////////////////////////////////////////////////////////////////////////// STDMETHODIMP HVMDKImage::COMGETTER(Description) (BSTR *aDescription) { if (!aDescription) return E_POINTER; AutoReaderLock alock (this); CHECK_READY(); mDescription.cloneTo (aDescription); return S_OK; } STDMETHODIMP HVMDKImage::COMSETTER(Description) (INPTR BSTR aDescription) { AutoLock alock (this); CHECK_READY(); CHECK_BUSY_AND_READERS(); return E_NOTIMPL; /// @todo (r=dmik) implement // // if (mState >= Created) // { // int vrc = VDISetImageComment (Utf8Str (mFilePathFull), Utf8Str (aDescription)); // if (VBOX_FAILURE (vrc)) // return setError (E_FAIL, // tr ("Could not change the description of the VDI hard disk '%ls' " // "(%Vrc)"), // toString().raw(), vrc); // } // // mDescription = aDescription; // return S_OK; } STDMETHODIMP HVMDKImage::COMGETTER(Size) (ULONG64 *aSize) { if (!aSize) return E_POINTER; AutoReaderLock alock (this); CHECK_READY(); /// @todo (r=dmik) will need this if we add support for differencing VMDKs // // /* only a non-differencing image knows the logical size */ // if (isDifferencing()) // return root()->COMGETTER(Size) (aSize); *aSize = mSize; return S_OK; } STDMETHODIMP HVMDKImage::COMGETTER(ActualSize) (ULONG64 *aActualSize) { if (!aActualSize) return E_POINTER; AutoReaderLock alock (this); CHECK_READY(); *aActualSize = mActualSize; return S_OK; } // IVirtualDiskImage properties //////////////////////////////////////////////////////////////////////////////// STDMETHODIMP HVMDKImage::COMGETTER(FilePath) (BSTR *aFilePath) { if (!aFilePath) return E_POINTER; AutoReaderLock alock (this); CHECK_READY(); mFilePathFull.cloneTo (aFilePath); return S_OK; } STDMETHODIMP HVMDKImage::COMSETTER(FilePath) (INPTR BSTR aFilePath) { AutoLock alock (this); CHECK_READY(); if (mState != NotCreated) return setError (E_ACCESSDENIED, tr ("Cannot change the file path of the existing hard disk '%ls'"), toString().raw()); CHECK_BUSY_AND_READERS(); /* append the default path if only a name is given */ Bstr path = aFilePath; if (aFilePath && *aFilePath) { Utf8Str fp = aFilePath; if (!RTPathHavePath (fp)) { AutoReaderLock propsLock (mVirtualBox->systemProperties()); path = Utf8StrFmt ("%ls%c%s", mVirtualBox->systemProperties()->defaultVDIFolder().raw(), RTPATH_DELIMITER, fp.raw()); } } return setFilePath (path); } STDMETHODIMP HVMDKImage::COMGETTER(Created) (BOOL *aCreated) { if (!aCreated) return E_POINTER; AutoReaderLock alock (this); CHECK_READY(); *aCreated = mState >= Created; return S_OK; } // IVMDKImage methods ///////////////////////////////////////////////////////////////////////////// STDMETHODIMP HVMDKImage::CreateDynamicImage (ULONG64 aSize, IProgress **aProgress) { if (!aProgress) return E_POINTER; AutoLock alock (this); CHECK_READY(); return createImage (aSize, TRUE /* aDynamic */, aProgress); } STDMETHODIMP HVMDKImage::CreateFixedImage (ULONG64 aSize, IProgress **aProgress) { if (!aProgress) return E_POINTER; AutoLock alock (this); CHECK_READY(); return createImage (aSize, FALSE /* aDynamic */, aProgress); } STDMETHODIMP HVMDKImage::DeleteImage() { AutoLock alock (this); CHECK_READY(); CHECK_BUSY_AND_READERS(); return E_NOTIMPL; /// @todo (r=dmik) later // We will need to parse the file in order to delete all related delta and // sparse images etc. We may also want to obey the .vmdk.lck file // which is (as far as I understood) created when the VMware VM is // running or saved etc. // // if (mRegistered) // return setError (E_ACCESSDENIED, // tr ("Cannot delete an image of the registered hard disk image '%ls"), // mFilePathFull.raw()); // if (mState == NotCreated) // return setError (E_FAIL, // tr ("Hard disk image has been already deleted or never created")); // // int vrc = RTFileDelete (Utf8Str (mFilePathFull)); // if (VBOX_FAILURE (vrc)) // return setError (E_FAIL, tr ("Could not delete the image file '%ls' (%Vrc)"), // mFilePathFull.raw(), vrc); // // mState = NotCreated; // // /* reset the fields */ // mSize = 0; // mActualSize = 0; // // return S_OK; } // public/protected methods for internal purposes only ///////////////////////////////////////////////////////////////////////////// /** * Attempts to mark the hard disk as registered. * Only VirtualBox can call this method. */ HRESULT HVMDKImage::trySetRegistered (BOOL aRegistered) { AutoLock alock (this); CHECK_READY(); if (aRegistered) { if (mState == NotCreated) return setError (E_FAIL, tr ("Image file '%ls' is not yet created for this hard disk"), mFilePathFull.raw()); /// @todo (r=dmik) will need this if we add support for differencing VMDKs // if (isDifferencing()) // return setError (E_FAIL, // tr ("Hard disk '%ls' is differencing and cannot be unregistered " // "explicitly"), // mFilePathFull.raw()); } else { ComAssertRet (mState >= Created, E_FAIL); } return HardDisk::trySetRegistered (aRegistered); } /** * Checks accessibility of this hard disk image only (w/o parents). * * @param aAccessError on output, a null string indicates the hard disk is * accessible, otherwise contains a message describing * the reason of inaccessibility. * * @note Locks this object for writing. */ HRESULT HVMDKImage::getAccessible (Bstr &aAccessError) { /* queryInformation() needs a write lock */ AutoLock alock (this); CHECK_READY(); if (mStateCheckSem != NIL_RTSEMEVENTMULTI) { /* An accessibility check in progress on some other thread, * wait for it to finish. */ ComAssertRet (mStateCheckWaiters != (ULONG) ~0, E_FAIL); ++ mStateCheckWaiters; alock.leave(); int vrc = RTSemEventMultiWait (mStateCheckSem, RT_INDEFINITE_WAIT); alock.enter(); AssertReturn (mStateCheckWaiters != 0, E_FAIL); -- mStateCheckWaiters; if (mStateCheckWaiters == 0) { RTSemEventMultiDestroy (mStateCheckSem); mStateCheckSem = NIL_RTSEMEVENTMULTI; } AssertRCReturn (vrc, E_FAIL); /* don't touch aAccessError, it has been already set */ return S_OK; } /* check the basic accessibility */ HRESULT rc = getBaseAccessible (aAccessError, true /* aCheckBusy */); if (FAILED (rc) || !aAccessError.isNull()) return rc; if (mState >= Created) { return queryInformation (&aAccessError); } aAccessError = Utf8StrFmt ("Hard disk image '%ls' is not yet created", mFilePathFull.raw()); return S_OK; } /** * Saves hard disk settings to the specified storage node and saves * all children to the specified hard disk node * * @param aHDNode or node. * @param aStorageNode node. */ HRESULT HVMDKImage::saveSettings (settings::Key &aHDNode, settings::Key &aStorageNode) { AssertReturn (!aHDNode.isNull() && !aStorageNode.isNull(), E_FAIL); AutoReaderLock alock (this); CHECK_READY(); /* filePath (required) */ aStorageNode.setValue ("filePath", mFilePath); /* save basic settings and children */ return HardDisk::saveSettings (aHDNode); } /** * Checks if the given change of \a aOldPath to \a aNewPath affects the path * of this hard disk and updates it if necessary to reflect the new location. * Intended to be from HardDisk::updatePaths(). * * @param aOldPath old path (full) * @param aNewPath new path (full) * * @note Locks this object for writing. */ void HVMDKImage::updatePath (const char *aOldPath, const char *aNewPath) { AssertReturnVoid (aOldPath); AssertReturnVoid (aNewPath); AutoLock alock (this); AssertReturnVoid (isReady()); size_t oldPathLen = strlen (aOldPath); Utf8Str path = mFilePathFull; LogFlowThisFunc (("VMDK.fullPath={%s}\n", path.raw())); if (RTPathStartsWith (path, aOldPath)) { Utf8Str newPath = Utf8StrFmt ("%s%s", aNewPath, path.raw() + oldPathLen); path = newPath; mVirtualBox->calculateRelativePath (path, path); unconst (mFilePathFull) = newPath; unconst (mFilePath) = path; LogFlowThisFunc (("-> updated: full={%s} short={%s}\n", newPath.raw(), path.raw())); } } /** * Returns the string representation of this hard disk. * When \a aShort is false, returns the full image file path. * Otherwise, returns the image file name only. * * @param aShort if true, a short representation is returned * * @note Locks this object for reading. */ Bstr HVMDKImage::toString (bool aShort /* = false */) { AutoReaderLock alock (this); if (!aShort) return mFilePathFull; else { Utf8Str fname = mFilePathFull; return RTPathFilename (fname.mutableRaw()); } } /** * Creates a clone of this hard disk by storing hard disk data in the given * VDI file. * * If the operation fails, @a aDeleteTarget will be set to @c true unless the * failure happened because the target file already existed. * * @param aId UUID to assign to the created image. * @param aTargetPath VDI file where the cloned image is to be to stored. * @param aProgress progress object to run during operation. * @param aDeleteTarget Whether it is recommended to delete target on * failure or not. */ HRESULT HVMDKImage::cloneToImage (const Guid &aId, const Utf8Str &aTargetPath, Progress *aProgress, bool &aDeleteTarget) { ComAssertMsgFailed (("Not implemented")); return E_NOTIMPL; /// @todo (r=dmik) will need this if we add support for differencing VMDKs // Use code from HVirtualDiskImage::cloneToImage as an example. } /** * Creates a new differencing image for this hard disk with the given * VDI file name. * * @param aId UUID to assign to the created image * @param aTargetPath VDI file where to store the created differencing image * @param aProgress progress object to run during operation * (can be NULL) */ HRESULT HVMDKImage::createDiffImage (const Guid &aId, const Utf8Str &aTargetPath, Progress *aProgress) { ComAssertMsgFailed (("Not implemented")); return E_NOTIMPL; /// @todo (r=dmik) will need this if we add support for differencing VMDKs // Use code from HVirtualDiskImage::createDiffImage as an example. } // private methods ///////////////////////////////////////////////////////////////////////////// /** * Helper to set a new file path. * Resolves a path relatively to the Virtual Box home directory. * * @note * Must be called from under the object's lock! */ HRESULT HVMDKImage::setFilePath (const BSTR aFilePath) { if (aFilePath && *aFilePath) { /* get the full file name */ char filePathFull [RTPATH_MAX]; int vrc = RTPathAbsEx (mVirtualBox->homeDir(), Utf8Str (aFilePath), filePathFull, sizeof (filePathFull)); if (VBOX_FAILURE (vrc)) return setError (E_FAIL, tr ("Invalid image file path '%ls' (%Vrc)"), aFilePath, vrc); mFilePath = aFilePath; mFilePathFull = filePathFull; } else { mFilePath.setNull(); mFilePathFull.setNull(); } return S_OK; } /** * Helper to query information about the VDI hard disk. * * @param aAccessError not used when NULL, otherwise see #getAccessible() * * @note Must be called from under the object's lock, only after * CHECK_BUSY_AND_READERS() succeeds. */ HRESULT HVMDKImage::queryInformation (Bstr *aAccessError) { AssertReturn (isLockedOnCurrentThread(), E_FAIL); /* create a lock object to completely release it later */ AutoLock alock (this); AssertReturn (mStateCheckWaiters == 0, E_FAIL); ComAssertRet (mState >= Created, E_FAIL); HRESULT rc = S_OK; int vrc = VINF_SUCCESS; /* lazily create a semaphore */ vrc = RTSemEventMultiCreate (&mStateCheckSem); ComAssertRCRet (vrc, E_FAIL); /* Reference the disk to prevent any concurrent modifications * after releasing the lock below (to unblock getters before * a lengthy operation). */ addReader(); alock.leave(); /* VBoxVHDD management interface needs to be optimized: we're opening a * file three times in a raw to get three bits of information. */ Utf8Str filePath = mFilePathFull; Bstr errMsg; /* reset any previous error report from VDError() */ mLastVDError.setNull(); do { Guid id, parentId; /// @todo changed from VD_OPEN_FLAGS_READONLY to VD_OPEN_FLAGS_NORMAL, /// because otherwise registering a VMDK which so far has no UUID will /// yield a null UUID. It cannot be added to a VMDK opened readonly, /// obviously. This of course changes locking behavior, but for now /// this is acceptable. A better solution needs to be found later. vrc = VDOpen (mContainer, "VMDK", filePath, VD_OPEN_FLAGS_NORMAL); if (VBOX_FAILURE (vrc)) break; vrc = VDGetUuid (mContainer, 0, id.ptr()); if (VBOX_FAILURE (vrc)) break; vrc = VDGetParentUuid (mContainer, 0, parentId.ptr()); if (VBOX_FAILURE (vrc)) break; if (!mId.isEmpty()) { /* check that the actual UUID of the image matches the stored UUID */ if (mId != id) { errMsg = Utf8StrFmt ( tr ("Actual UUID {%Vuuid} of the hard disk image '%s' doesn't " "match UUID {%Vuuid} stored in the registry"), id.ptr(), filePath.raw(), mId.ptr()); break; } } else { /* assgn an UUID read from the image file */ mId = id; } if (mParent) { /* check parent UUID */ AutoLock parentLock (mParent); if (mParent->id() != parentId) { errMsg = Utf8StrFmt ( tr ("UUID {%Vuuid} of the parent image '%ls' stored in " "the hard disk image file '%s' doesn't match " "UUID {%Vuuid} stored in the registry"), parentId.raw(), mParent->toString().raw(), filePath.raw(), mParent->id().raw()); break; } } else if (!parentId.isEmpty()) { errMsg = Utf8StrFmt ( tr ("Hard disk image '%s' is a differencing image that is linked " "to a hard disk with UUID {%Vuuid} and cannot be used " "directly as a base hard disk"), filePath.raw(), parentId.raw()); break; } /* get actual file size */ /// @todo is there a direct method in RT? { RTFILE file = NIL_RTFILE; vrc = RTFileOpen (&file, filePath, RTFILE_O_READ); if (VBOX_SUCCESS (vrc)) { uint64_t size = 0; vrc = RTFileGetSize (file, &size); if (VBOX_SUCCESS (vrc)) mActualSize = size; RTFileClose (file); } if (VBOX_FAILURE (vrc)) break; } /* query logical size only for non-differencing images */ if (!mParent) { uint64_t size = VDGetSize (mContainer, 0); /* convert to MBytes */ mSize = size / 1024 / 1024; } } while (0); VDCloseAll (mContainer); /* enter the lock again */ alock.enter(); /* remove the reference */ releaseReader(); if (FAILED (rc) || VBOX_FAILURE (vrc) || !errMsg.isNull()) { LogWarningFunc (("'%ls' is not accessible " "(rc=%08X, vrc=%Vrc, errMsg='%ls', mLastVDError='%s')\n", mFilePathFull.raw(), rc, vrc, errMsg.raw(), mLastVDError.raw())); if (aAccessError) { if (!errMsg.isNull()) *aAccessError = errMsg; else if (!mLastVDError.isNull()) *aAccessError = mLastVDError; else if (VBOX_FAILURE (vrc)) *aAccessError = Utf8StrFmt ( tr ("Could not access hard disk image '%ls' (%Vrc)"), mFilePathFull.raw(), vrc); } /* downgrade to not accessible */ mState = Created; } else { if (aAccessError) aAccessError->setNull(); mState = Accessible; } /* inform waiters if there are any */ if (mStateCheckWaiters > 0) { RTSemEventMultiSignal (mStateCheckSem); } else { /* delete the semaphore ourselves */ RTSemEventMultiDestroy (mStateCheckSem); mStateCheckSem = NIL_RTSEMEVENTMULTI; } /* cleanup the last error report from VDError() */ mLastVDError.setNull(); return rc; } /** * Helper to create hard disk images. * * @param aSize size in MB * @param aDynamic dynamic or fixed image * @param aProgress address of IProgress pointer to return */ HRESULT HVMDKImage::createImage (ULONG64 aSize, BOOL aDynamic, IProgress **aProgress) { ComAssertMsgFailed (("Not implemented")); return E_NOTIMPL; /// @todo (r=dmik) later // Use code from HVirtualDiskImage::createImage as an example. } /* static */ DECLCALLBACK(int) HVMDKImage::VDITaskThread (RTTHREAD thread, void *pvUser) { AssertMsgFailed (("Not implemented")); return VERR_GENERAL_FAILURE; /// @todo (r=dmik) later // Use code from HVirtualDiskImage::VDITaskThread as an example. } /* static */ DECLCALLBACK(void) HVMDKImage::VDError (void *pvUser, int rc, RT_SRC_POS_DECL, const char *pszFormat, va_list va) { HVMDKImage *that = static_cast (pvUser); AssertReturnVoid (that != NULL); /// @todo pass the error message to the operation initiator Utf8Str err = Utf8StrFmtVA (pszFormat, va); if (VBOX_FAILURE (rc)) err = Utf8StrFmt ("%s (%Vrc)", err.raw(), rc); if (that->mLastVDError.isNull()) that->mLastVDError = err; else that->mLastVDError = Utf8StrFmt ("%s.\n%s", that->mLastVDError.raw(), err.raw()); } //////////////////////////////////////////////////////////////////////////////// // HCustomHardDisk class //////////////////////////////////////////////////////////////////////////////// // constructor / destructor //////////////////////////////////////////////////////////////////////////////// HRESULT HCustomHardDisk::FinalConstruct() { HRESULT rc = HardDisk::FinalConstruct(); if (FAILED (rc)) return rc; mState = NotCreated; mStateCheckSem = NIL_RTSEMEVENTMULTI; mStateCheckWaiters = 0; mSize = 0; mActualSize = 0; mContainer = NULL; ComAssertRCRet (rc, E_FAIL); return S_OK; } void HCustomHardDisk::FinalRelease() { if (mContainer != NULL) VDDestroy (mContainer); HardDisk::FinalRelease(); } // public initializer/uninitializer for internal purposes only //////////////////////////////////////////////////////////////////////////////// // public methods for internal purposes only ///////////////////////////////////////////////////////////////////////////// /** * Initializes the custom hard disk object by reading its properties from * the given configuration node. The created hard disk will be marked as * registered on success. * * @param aHDNode node. * @param aCustomNode node. */ HRESULT HCustomHardDisk::init (VirtualBox *aVirtualBox, HardDisk *aParent, const settings::Key &aHDNode, const settings::Key &aCustomNode) { using namespace settings; LogFlowThisFunc (("\n")); AssertReturn (!aHDNode.isNull() && !aCustomNode.isNull(), E_FAIL); AutoLock alock (this); ComAssertRet (!isReady(), E_UNEXPECTED); mStorageType = HardDiskStorageType_CustomHardDisk; HRESULT rc = S_OK; int vrc = VINF_SUCCESS; do { rc = protectedInit (aVirtualBox, aParent); CheckComRCBreakRC (rc); /* set ready to let protectedUninit() be called on failure */ setReady (true); /* location (required) */ Bstr location = aCustomNode.stringValue ("location"); rc = setLocation (location); CheckComRCBreakRC (rc); LogFlowThisFunc (("'%ls'\n", mLocationFull.raw())); /* format (required) */ mFormat = aCustomNode.stringValue ("format"); /* initialize the container */ vrc = VDCreate (VDError, this, &mContainer); if (VBOX_FAILURE (vrc)) { AssertRC (vrc); if (mLastVDError.isEmpty()) rc = setError (E_FAIL, tr ("Unknown format '%ls' of the custom " "hard disk '%ls' (%Vrc)"), mFormat.raw(), toString().raw(), vrc); else rc = setErrorBstr (E_FAIL, mLastVDError); break; } /* load basic settings and children */ rc = loadSettings (aHDNode); CheckComRCBreakRC (rc); if (mType != HardDiskType_Writethrough) { rc = setError (E_FAIL, tr ("Currently, non-Writethrough custom hard disks " "are not allowed ('%ls')"), toString().raw()); break; } mState = Created; mRegistered = TRUE; /* Don't call queryInformation() for registered hard disks to * prevent the calling thread (i.e. the VirtualBox server startup * thread) from an unexpected freeze. The vital mId property (UUID) * is read from the registry file in loadSettings(). To get the rest, * the user will have to call COMGETTER(Accessible) manually. */ } while (0); if (FAILED (rc)) uninit(); return rc; } /** * Initializes the custom hard disk object using the given image file name. * * @param aVirtualBox VirtualBox parent. * @param aParent Currently, must always be @c NULL. * @param aLocation Location of the virtual disk, or @c NULL to create an * image-less object. * @param aRegistered Whether to mark this disk as registered or not * (ignored when @a aLocation is @c NULL, assuming @c FALSE) */ HRESULT HCustomHardDisk::init (VirtualBox *aVirtualBox, HardDisk *aParent, const BSTR aLocation, BOOL aRegistered /* = FALSE */) { LogFlowThisFunc (("aLocation='%ls', aRegistered=%d\n", aLocation, aRegistered)); AssertReturn (aParent == NULL, E_FAIL); AutoLock alock (this); ComAssertRet (!isReady(), E_UNEXPECTED); mStorageType = HardDiskStorageType_CustomHardDisk; HRESULT rc = S_OK; do { rc = protectedInit (aVirtualBox, aParent); CheckComRCBreakRC (rc); /* set ready to let protectedUninit() be called on failure */ setReady (true); rc = setLocation (aLocation); CheckComRCBreakRC (rc); /* currently, all custom hard disks are writethrough */ mType = HardDiskType_Writethrough; Assert (mId.isEmpty()); if (aLocation && *aLocation) { mRegistered = aRegistered; mState = Created; char *pszFormat = NULL; int vrc = VDGetFormat (Utf8Str (mLocation), &pszFormat); if (VBOX_FAILURE(vrc)) { AssertRC (vrc); rc = setError (E_FAIL, tr ("Cannot recognize the format of the custom " "hard disk '%ls' (%Vrc)"), toString().raw(), vrc); break; } mFormat = Bstr (pszFormat); RTStrFree (pszFormat); /* Create the corresponding container. */ vrc = VDCreate (VDError, this, &mContainer); /* the format has been already checked for presence at this point */ ComAssertRCBreak (vrc, rc = E_FAIL); /* Call queryInformation() anyway (even if it will block), because * it is the only way to get the UUID of the existing VDI and * initialize the vital mId property. */ Bstr errMsg; rc = queryInformation (&errMsg); if (SUCCEEDED (rc)) { /* We are constructing a new HVirtualDiskImage object. If there * is a fatal accessibility error (we cannot read image UUID), * we have to fail. We do so even on non-fatal errors as well, * because it's not worth to keep going with the inaccessible * image from the very beginning (when nothing else depends on * it yet). */ if (!errMsg.isNull()) rc = setErrorBstr (E_FAIL, errMsg); } } else { mRegistered = FALSE; mState = NotCreated; mId.create(); } } while (0); if (FAILED (rc)) uninit(); return rc; } /** * Uninitializes the instance and sets the ready flag to FALSE. * Called either from FinalRelease(), by the parent when it gets destroyed, * or by a third party when it decides this object is no more valid. */ void HCustomHardDisk::uninit() { LogFlowThisFunc (("\n")); AutoLock alock (this); if (!isReady()) return; HardDisk::protectedUninit (alock); } // IHardDisk properties //////////////////////////////////////////////////////////////////////////////// STDMETHODIMP HCustomHardDisk::COMGETTER(Description) (BSTR *aDescription) { if (!aDescription) return E_POINTER; AutoReaderLock alock (this); CHECK_READY(); mDescription.cloneTo (aDescription); return S_OK; } STDMETHODIMP HCustomHardDisk::COMSETTER(Description) (INPTR BSTR aDescription) { AutoLock alock (this); CHECK_READY(); CHECK_BUSY_AND_READERS(); return E_NOTIMPL; } STDMETHODIMP HCustomHardDisk::COMGETTER(Size) (ULONG64 *aSize) { if (!aSize) return E_POINTER; AutoReaderLock alock (this); CHECK_READY(); *aSize = mSize; return S_OK; } STDMETHODIMP HCustomHardDisk::COMGETTER(ActualSize) (ULONG64 *aActualSize) { if (!aActualSize) return E_POINTER; AutoReaderLock alock (this); CHECK_READY(); *aActualSize = mActualSize; return S_OK; } // ICustomHardDisk properties //////////////////////////////////////////////////////////////////////////////// STDMETHODIMP HCustomHardDisk::COMGETTER(Location) (BSTR *aLocation) { if (!aLocation) return E_POINTER; AutoReaderLock alock (this); CHECK_READY(); mLocationFull.cloneTo (aLocation); return S_OK; } STDMETHODIMP HCustomHardDisk::COMSETTER(Location) (INPTR BSTR aLocation) { AutoLock alock (this); CHECK_READY(); if (mState != NotCreated) return setError (E_ACCESSDENIED, tr ("Cannot change the file path of the existing hard disk '%ls'"), toString().raw()); CHECK_BUSY_AND_READERS(); /// @todo currently, we assume that location is always a file path for /// all custom hard disks. This is not generally correct, and needs to be /// parametrized in the VD plugin interface. /* append the default path if only a name is given */ Bstr path = aLocation; if (aLocation && *aLocation) { Utf8Str fp = aLocation; if (!RTPathHavePath (fp)) { AutoReaderLock propsLock (mVirtualBox->systemProperties()); path = Utf8StrFmt ("%ls%c%s", mVirtualBox->systemProperties()->defaultVDIFolder().raw(), RTPATH_DELIMITER, fp.raw()); } } return setLocation (path); } STDMETHODIMP HCustomHardDisk::COMGETTER(Created) (BOOL *aCreated) { if (!aCreated) return E_POINTER; AutoReaderLock alock (this); CHECK_READY(); *aCreated = mState >= Created; return S_OK; } STDMETHODIMP HCustomHardDisk::COMGETTER(Format) (BSTR *aFormat) { if (!aFormat) return E_POINTER; AutoReaderLock alock (this); CHECK_READY(); mFormat.cloneTo (aFormat); return S_OK; } // ICustomHardDisk methods ///////////////////////////////////////////////////////////////////////////// STDMETHODIMP HCustomHardDisk::CreateDynamicImage (ULONG64 aSize, IProgress **aProgress) { if (!aProgress) return E_POINTER; AutoLock alock (this); CHECK_READY(); return createImage (aSize, TRUE /* aDynamic */, aProgress); } STDMETHODIMP HCustomHardDisk::CreateFixedImage (ULONG64 aSize, IProgress **aProgress) { if (!aProgress) return E_POINTER; AutoLock alock (this); CHECK_READY(); return createImage (aSize, FALSE /* aDynamic */, aProgress); } STDMETHODIMP HCustomHardDisk::DeleteImage() { AutoLock alock (this); CHECK_READY(); CHECK_BUSY_AND_READERS(); return E_NOTIMPL; /// @todo later } // public/protected methods for internal purposes only ///////////////////////////////////////////////////////////////////////////// /** * Attempts to mark the hard disk as registered. * Only VirtualBox can call this method. */ HRESULT HCustomHardDisk::trySetRegistered (BOOL aRegistered) { AutoLock alock (this); CHECK_READY(); if (aRegistered) { if (mState == NotCreated) return setError (E_FAIL, tr ("Storage location '%ls' is not yet created for this hard disk"), mLocationFull.raw()); } else { ComAssertRet (mState >= Created, E_FAIL); } return HardDisk::trySetRegistered (aRegistered); } /** * Checks accessibility of this hard disk image only (w/o parents). * * @param aAccessError on output, a null string indicates the hard disk is * accessible, otherwise contains a message describing * the reason of inaccessibility. * * @note Locks this object for writing. */ HRESULT HCustomHardDisk::getAccessible (Bstr &aAccessError) { /* queryInformation() needs a write lock */ AutoLock alock (this); CHECK_READY(); if (mStateCheckSem != NIL_RTSEMEVENTMULTI) { /* An accessibility check in progress on some other thread, * wait for it to finish. */ ComAssertRet (mStateCheckWaiters != (ULONG) ~0, E_FAIL); ++ mStateCheckWaiters; alock.leave(); int vrc = RTSemEventMultiWait (mStateCheckSem, RT_INDEFINITE_WAIT); alock.enter(); AssertReturn (mStateCheckWaiters != 0, E_FAIL); -- mStateCheckWaiters; if (mStateCheckWaiters == 0) { RTSemEventMultiDestroy (mStateCheckSem); mStateCheckSem = NIL_RTSEMEVENTMULTI; } AssertRCReturn (vrc, E_FAIL); /* don't touch aAccessError, it has been already set */ return S_OK; } /* check the basic accessibility */ HRESULT rc = getBaseAccessible (aAccessError, true /* aCheckBusy */); if (FAILED (rc) || !aAccessError.isNull()) return rc; if (mState >= Created) { return queryInformation (&aAccessError); } aAccessError = Utf8StrFmt ("Hard disk '%ls' is not yet created", mLocationFull.raw()); return S_OK; } /** * Saves hard disk settings to the specified storage node and saves * all children to the specified hard disk node * * @param aHDNode or node. * @param aStorageNode node. */ HRESULT HCustomHardDisk::saveSettings (settings::Key &aHDNode, settings::Key &aStorageNode) { AssertReturn (!aHDNode.isNull() && !aStorageNode.isNull(), E_FAIL); AutoReaderLock alock (this); CHECK_READY(); /* location (required) */ aStorageNode.setValue ("location", mLocationFull); /* format (required) */ aStorageNode.setValue ("format", mFormat); /* save basic settings and children */ return HardDisk::saveSettings (aHDNode); } /** * Returns the string representation of this hard disk. * When \a aShort is false, returns the full image file path. * Otherwise, returns the image file name only. * * @param aShort if true, a short representation is returned * * @note Locks this object for reading. */ Bstr HCustomHardDisk::toString (bool aShort /* = false */) { AutoReaderLock alock (this); /// @todo currently, we assume that location is always a file path for /// all custom hard disks. This is not generally correct, and needs to be /// parametrized in the VD plugin interface. if (!aShort) return mLocationFull; else { Utf8Str fname = mLocationFull; return RTPathFilename (fname.mutableRaw()); } } /** * Creates a clone of this hard disk by storing hard disk data in the given * VDI file. * * If the operation fails, @a aDeleteTarget will be set to @c true unless the * failure happened because the target file already existed. * * @param aId UUID to assign to the created image. * @param aTargetPath VDI file where the cloned image is to be to stored. * @param aProgress progress object to run during operation. * @param aDeleteTarget Whether it is recommended to delete target on * failure or not. */ HRESULT HCustomHardDisk::cloneToImage (const Guid &aId, const Utf8Str &aTargetPath, Progress *aProgress, bool &aDeleteTarget) { ComAssertMsgFailed (("Not implemented")); return E_NOTIMPL; } /** * Creates a new differencing image for this hard disk with the given * VDI file name. * * @param aId UUID to assign to the created image * @param aTargetPath VDI file where to store the created differencing image * @param aProgress progress object to run during operation * (can be NULL) */ HRESULT HCustomHardDisk::createDiffImage (const Guid &aId, const Utf8Str &aTargetPath, Progress *aProgress) { ComAssertMsgFailed (("Not implemented")); return E_NOTIMPL; } // private methods ///////////////////////////////////////////////////////////////////////////// /** * Helper to set a new location. * * @note * Must be called from under the object's lock! */ HRESULT HCustomHardDisk::setLocation (const BSTR aLocation) { /// @todo currently, we assume that location is always a file path for /// all custom hard disks. This is not generally correct, and needs to be /// parametrized in the VD plugin interface. if (aLocation && *aLocation) { /* get the full file name */ char locationFull [RTPATH_MAX]; int vrc = RTPathAbsEx (mVirtualBox->homeDir(), Utf8Str (aLocation), locationFull, sizeof (locationFull)); if (VBOX_FAILURE (vrc)) return setError (E_FAIL, tr ("Invalid hard disk location '%ls' (%Vrc)"), aLocation, vrc); mLocation = aLocation; mLocationFull = locationFull; } else { mLocation.setNull(); mLocationFull.setNull(); } return S_OK; } /** * Helper to query information about the custom hard disk. * * @param aAccessError not used when NULL, otherwise see #getAccessible() * * @note Must be called from under the object's lock, only after * CHECK_BUSY_AND_READERS() succeeds. */ HRESULT HCustomHardDisk::queryInformation (Bstr *aAccessError) { AssertReturn (isLockedOnCurrentThread(), E_FAIL); /* create a lock object to completely release it later */ AutoLock alock (this); AssertReturn (mStateCheckWaiters == 0, E_FAIL); ComAssertRet (mState >= Created, E_FAIL); HRESULT rc = S_OK; int vrc = VINF_SUCCESS; /* lazily create a semaphore */ vrc = RTSemEventMultiCreate (&mStateCheckSem); ComAssertRCRet (vrc, E_FAIL); /* Reference the disk to prevent any concurrent modifications * after releasing the lock below (to unblock getters before * a lengthy operation). */ addReader(); alock.leave(); /* VBoxVHDD management interface needs to be optimized: we're opening a * file three times in a raw to get three bits of information. */ Utf8Str location = mLocationFull; Bstr errMsg; /* reset any previous error report from VDError() */ mLastVDError.setNull(); do { Guid id, parentId; vrc = VDOpen (mContainer, Utf8Str (mFormat), location, VD_OPEN_FLAGS_INFO); if (VBOX_FAILURE (vrc)) break; vrc = VDGetUuid (mContainer, 0, id.ptr()); if (VBOX_FAILURE (vrc)) break; vrc = VDGetParentUuid (mContainer, 0, parentId.ptr()); if (VBOX_FAILURE (vrc)) break; if (!mId.isEmpty()) { /* check that the actual UUID of the image matches the stored UUID */ if (mId != id) { errMsg = Utf8StrFmt ( tr ("Actual UUID {%Vuuid} of the hard disk image '%s' doesn't " "match UUID {%Vuuid} stored in the registry"), id.ptr(), location.raw(), mId.ptr()); break; } } else { /* assgn an UUID read from the image file */ mId = id; } if (mParent) { /* check parent UUID */ AutoLock parentLock (mParent); if (mParent->id() != parentId) { errMsg = Utf8StrFmt ( tr ("UUID {%Vuuid} of the parent image '%ls' stored in " "the hard disk image file '%s' doesn't match " "UUID {%Vuuid} stored in the registry"), parentId.raw(), mParent->toString().raw(), location.raw(), mParent->id().raw()); break; } } else if (!parentId.isEmpty()) { errMsg = Utf8StrFmt ( tr ("Hard disk image '%s' is a differencing image that is linked " "to a hard disk with UUID {%Vuuid} and cannot be used " "directly as a base hard disk"), location.raw(), parentId.raw()); break; } /* get actual file size */ /// @todo is there a direct method in RT? { RTFILE file = NIL_RTFILE; vrc = RTFileOpen (&file, location, RTFILE_O_READ); if (VBOX_SUCCESS (vrc)) { uint64_t size = 0; vrc = RTFileGetSize (file, &size); if (VBOX_SUCCESS (vrc)) mActualSize = size; RTFileClose (file); } if (VBOX_FAILURE (vrc)) break; } /* query logical size only for non-differencing images */ if (!mParent) { uint64_t size = VDGetSize (mContainer, 0); /* convert to MBytes */ mSize = size / 1024 / 1024; } } while (0); VDCloseAll (mContainer); /* enter the lock again */ alock.enter(); /* remove the reference */ releaseReader(); if (FAILED (rc) || VBOX_FAILURE (vrc) || !errMsg.isNull()) { LogWarningFunc (("'%ls' is not accessible " "(rc=%08X, vrc=%Vrc, errMsg='%ls')\n", mLocationFull.raw(), rc, vrc, errMsg.raw())); if (aAccessError) { if (!errMsg.isNull()) *aAccessError = errMsg; else if (!mLastVDError.isNull()) *aAccessError = mLastVDError; else if (VBOX_FAILURE (vrc)) *aAccessError = Utf8StrFmt ( tr ("Could not access hard disk '%ls' (%Vrc)"), mLocationFull.raw(), vrc); } /* downgrade to not accessible */ mState = Created; } else { if (aAccessError) aAccessError->setNull(); mState = Accessible; } /* inform waiters if there are any */ if (mStateCheckWaiters > 0) { RTSemEventMultiSignal (mStateCheckSem); } else { /* delete the semaphore ourselves */ RTSemEventMultiDestroy (mStateCheckSem); mStateCheckSem = NIL_RTSEMEVENTMULTI; } /* cleanup the last error report from VDError() */ mLastVDError.setNull(); return rc; } /** * Helper to create hard disk images. * * @param aSize size in MB * @param aDynamic dynamic or fixed image * @param aProgress address of IProgress pointer to return */ HRESULT HCustomHardDisk::createImage (ULONG64 aSize, BOOL aDynamic, IProgress **aProgress) { ComAssertMsgFailed (("Not implemented")); return E_NOTIMPL; } /* static */ DECLCALLBACK(int) HCustomHardDisk::VDITaskThread (RTTHREAD thread, void *pvUser) { AssertMsgFailed (("Not implemented")); return VERR_GENERAL_FAILURE; } /* static */ DECLCALLBACK(void) HCustomHardDisk::VDError (void *pvUser, int rc, RT_SRC_POS_DECL, const char *pszFormat, va_list va) { HCustomHardDisk *that = static_cast (pvUser); AssertReturnVoid (that != NULL); /// @todo pass the error message to the operation initiator Utf8Str err = Utf8StrFmtVA (pszFormat, va); if (VBOX_FAILURE (rc)) err = Utf8StrFmt ("%s (%Vrc)", err.raw(), rc); if (that->mLastVDError.isNull()) that->mLastVDError = err; else that->mLastVDError = Utf8StrFmt ("%s.\n%s", that->mLastVDError.raw(), err.raw()); } //////////////////////////////////////////////////////////////////////////////// // HVHDImage class //////////////////////////////////////////////////////////////////////////////// // constructor / destructor //////////////////////////////////////////////////////////////////////////////// HRESULT HVHDImage::FinalConstruct() { HRESULT rc = HardDisk::FinalConstruct(); if (FAILED (rc)) return rc; mState = NotCreated; mStateCheckSem = NIL_RTSEMEVENTMULTI; mStateCheckWaiters = 0; mSize = 0; mActualSize = 0; /* initialize the container */ int vrc = VDCreate (VDError, this, &mContainer); ComAssertRCRet (vrc, E_FAIL); return S_OK; } void HVHDImage::FinalRelease() { if (mContainer != NULL) VDDestroy (mContainer); HardDisk::FinalRelease(); } // public initializer/uninitializer for internal purposes only //////////////////////////////////////////////////////////////////////////////// // public methods for internal purposes only ///////////////////////////////////////////////////////////////////////////// /** * Initializes the VHD hard disk object by reading its properties from * the given configuration node. The created hard disk will be marked as * registered on success. * * @param aHDNode node * @param aVHDNode node */ HRESULT HVHDImage::init (VirtualBox *aVirtualBox, HardDisk *aParent, const settings::Key &aHDNode, const settings::Key &aVHDNode) { LogFlowThisFunc (("\n")); AssertReturn (!aHDNode.isNull() && !aVHDNode.isNull(), E_FAIL); AutoLock alock (this); ComAssertRet (!isReady(), E_UNEXPECTED); mStorageType = HardDiskStorageType_VHDImage; HRESULT rc = S_OK; do { rc = protectedInit (aVirtualBox, aParent); CheckComRCBreakRC (rc); /* set ready to let protectedUninit() be called on failure */ setReady (true); /* filePath (required) */ Bstr filePath = aVHDNode.stringValue("filePath"); rc = setFilePath (filePath); CheckComRCBreakRC (rc); LogFlowThisFunc (("'%ls'\n", mFilePathFull.raw())); /* load basic settings and children */ rc = loadSettings (aHDNode); CheckComRCBreakRC (rc); if (mType != HardDiskType_Writethrough) { rc = setError (E_FAIL, tr ("Currently, non-Writethrough VHD images are not " "allowed ('%ls')"), toString().raw()); break; } mState = Created; mRegistered = TRUE; /* Don't call queryInformation() for registered hard disks to * prevent the calling thread (i.e. the VirtualBox server startup * thread) from an unexpected freeze. The vital mId property (UUID) * is read from the registry file in loadSettings(). To get the rest, * the user will have to call COMGETTER(Accessible) manually. */ } while (0); if (FAILED (rc)) uninit(); return rc; } /** * Initializes the VHD hard disk object using the given image file name. * * @param aVirtualBox VirtualBox parent. * @param aParent Currently, must always be @c NULL. * @param aFilePath Path to the image file, or @c NULL to create an * image-less object. * @param aRegistered Whether to mark this disk as registered or not * (ignored when @a aFilePath is @c NULL, assuming @c FALSE) */ HRESULT HVHDImage::init (VirtualBox *aVirtualBox, HardDisk *aParent, const BSTR aFilePath, BOOL aRegistered /* = FALSE */) { LogFlowThisFunc (("aFilePath='%ls', aRegistered=%d\n", aFilePath, aRegistered)); AssertReturn (aParent == NULL, E_FAIL); AutoLock alock (this); ComAssertRet (!isReady(), E_UNEXPECTED); mStorageType = HardDiskStorageType_VHDImage; HRESULT rc = S_OK; do { rc = protectedInit (aVirtualBox, aParent); CheckComRCBreakRC (rc); /* set ready to let protectedUninit() be called on failure */ setReady (true); rc = setFilePath (aFilePath); CheckComRCBreakRC (rc); /* currently, all VHD hard disks are writethrough */ mType = HardDiskType_Writethrough; Assert (mId.isEmpty()); if (aFilePath && *aFilePath) { mRegistered = aRegistered; mState = Created; /* Call queryInformation() anyway (even if it will block), because * it is the only way to get the UUID of the existing VDI and * initialize the vital mId property. */ Bstr errMsg; rc = queryInformation (&errMsg); if (SUCCEEDED (rc)) { /* We are constructing a new HVirtualDiskImage object. If there * is a fatal accessibility error (we cannot read image UUID), * we have to fail. We do so even on non-fatal errors as well, * because it's not worth to keep going with the inaccessible * image from the very beginning (when nothing else depends on * it yet). */ if (!errMsg.isNull()) rc = setErrorBstr (E_FAIL, errMsg); } } else { mRegistered = FALSE; mState = NotCreated; mId.create(); } } while (0); if (FAILED (rc)) uninit(); return rc; } /** * Uninitializes the instance and sets the ready flag to FALSE. * Called either from FinalRelease(), by the parent when it gets destroyed, * or by a third party when it decides this object is no more valid. */ void HVHDImage::uninit() { LogFlowThisFunc (("\n")); AutoLock alock (this); if (!isReady()) return; HardDisk::protectedUninit (alock); } // IHardDisk properties //////////////////////////////////////////////////////////////////////////////// STDMETHODIMP HVHDImage::COMGETTER(Description) (BSTR *aDescription) { if (!aDescription) return E_POINTER; AutoReaderLock alock (this); CHECK_READY(); mDescription.cloneTo (aDescription); return S_OK; } STDMETHODIMP HVHDImage::COMSETTER(Description) (INPTR BSTR aDescription) { AutoLock alock (this); CHECK_READY(); CHECK_BUSY_AND_READERS(); return E_NOTIMPL; /// @todo implement // // if (mState >= Created) // { // int vrc = VDISetImageComment (Utf8Str (mFilePathFull), Utf8Str (aDescription)); // if (VBOX_FAILURE (vrc)) // return setError (E_FAIL, // tr ("Could not change the description of the VDI hard disk '%ls' " // "(%Vrc)"), // toString().raw(), vrc); // } // // mDescription = aDescription; // return S_OK; } STDMETHODIMP HVHDImage::COMGETTER(Size) (ULONG64 *aSize) { if (!aSize) return E_POINTER; AutoReaderLock alock (this); CHECK_READY(); /// @todo will need this if we add suppord for differencing VMDKs // // /* only a non-differencing image knows the logical size */ // if (isDifferencing()) // return root()->COMGETTER(Size) (aSize); *aSize = mSize; return S_OK; } STDMETHODIMP HVHDImage::COMGETTER(ActualSize) (ULONG64 *aActualSize) { if (!aActualSize) return E_POINTER; AutoReaderLock alock (this); CHECK_READY(); *aActualSize = mActualSize; return S_OK; } // IVirtualDiskImage properties //////////////////////////////////////////////////////////////////////////////// STDMETHODIMP HVHDImage::COMGETTER(FilePath) (BSTR *aFilePath) { if (!aFilePath) return E_POINTER; AutoReaderLock alock (this); CHECK_READY(); mFilePathFull.cloneTo (aFilePath); return S_OK; } STDMETHODIMP HVHDImage::COMSETTER(FilePath) (INPTR BSTR aFilePath) { AutoLock alock (this); CHECK_READY(); if (mState != NotCreated) return setError (E_ACCESSDENIED, tr ("Cannot change the file path of the existing hard disk '%ls'"), toString().raw()); CHECK_BUSY_AND_READERS(); /* append the default path if only a name is given */ Bstr path = aFilePath; if (aFilePath && *aFilePath) { Utf8Str fp = aFilePath; if (!RTPathHavePath (fp)) { AutoReaderLock propsLock (mVirtualBox->systemProperties()); path = Utf8StrFmt ("%ls%c%s", mVirtualBox->systemProperties()->defaultVDIFolder().raw(), RTPATH_DELIMITER, fp.raw()); } } return setFilePath (path); } STDMETHODIMP HVHDImage::COMGETTER(Created) (BOOL *aCreated) { if (!aCreated) return E_POINTER; AutoReaderLock alock (this); CHECK_READY(); *aCreated = mState >= Created; return S_OK; } // IVHDImage methods ///////////////////////////////////////////////////////////////////////////// STDMETHODIMP HVHDImage::CreateDynamicImage (ULONG64 aSize, IProgress **aProgress) { if (!aProgress) return E_POINTER; AutoLock alock (this); CHECK_READY(); return createImage (aSize, TRUE /* aDynamic */, aProgress); } STDMETHODIMP HVHDImage::CreateFixedImage (ULONG64 aSize, IProgress **aProgress) { if (!aProgress) return E_POINTER; AutoLock alock (this); CHECK_READY(); return createImage (aSize, FALSE /* aDynamic */, aProgress); } STDMETHODIMP HVHDImage::DeleteImage() { AutoLock alock (this); CHECK_READY(); CHECK_BUSY_AND_READERS(); return E_NOTIMPL; /// @todo later // We will need to parse the file in order to delete all related delta and // sparse images etc. We may also want to obey the .vmdk.lck file // which is (as far as I understood) created when the VMware VM is // running or saved etc. // // if (mRegistered) // return setError (E_ACCESSDENIED, // tr ("Cannot delete an image of the registered hard disk image '%ls"), // mFilePathFull.raw()); // if (mState == NotCreated) // return setError (E_FAIL, // tr ("Hard disk image has been already deleted or never created")); // // int vrc = RTFileDelete (Utf8Str (mFilePathFull)); // if (VBOX_FAILURE (vrc)) // return setError (E_FAIL, tr ("Could not delete the image file '%ls' (%Vrc)"), // mFilePathFull.raw(), vrc); // // mState = NotCreated; // // /* reset the fields */ // mSize = 0; // mActualSize = 0; // // return S_OK; } // public/protected methods for internal purposes only ///////////////////////////////////////////////////////////////////////////// /** * Attempts to mark the hard disk as registered. * Only VirtualBox can call this method. */ HRESULT HVHDImage::trySetRegistered (BOOL aRegistered) { AutoLock alock (this); CHECK_READY(); if (aRegistered) { if (mState == NotCreated) return setError (E_FAIL, tr ("Image file '%ls' is not yet created for this hard disk"), mFilePathFull.raw()); /// @todo will need this if we add suppord for differencing VHDs // if (isDifferencing()) // return setError (E_FAIL, // tr ("Hard disk '%ls' is differencing and cannot be unregistered " // "explicitly"), // mFilePathFull.raw()); } else { ComAssertRet (mState >= Created, E_FAIL); } return HardDisk::trySetRegistered (aRegistered); } /** * Checks accessibility of this hard disk image only (w/o parents). * * @param aAccessError on output, a null string indicates the hard disk is * accessible, otherwise contains a message describing * the reason of inaccessibility. * * @note Locks this object for writing. */ HRESULT HVHDImage::getAccessible (Bstr &aAccessError) { /* queryInformation() needs a write lock */ AutoLock alock (this); CHECK_READY(); if (mStateCheckSem != NIL_RTSEMEVENTMULTI) { /* An accessibility check in progress on some other thread, * wait for it to finish. */ ComAssertRet (mStateCheckWaiters != (ULONG) ~0, E_FAIL); ++ mStateCheckWaiters; alock.leave(); int vrc = RTSemEventMultiWait (mStateCheckSem, RT_INDEFINITE_WAIT); alock.enter(); AssertReturn (mStateCheckWaiters != 0, E_FAIL); -- mStateCheckWaiters; if (mStateCheckWaiters == 0) { RTSemEventMultiDestroy (mStateCheckSem); mStateCheckSem = NIL_RTSEMEVENTMULTI; } AssertRCReturn (vrc, E_FAIL); /* don't touch aAccessError, it has been already set */ return S_OK; } /* check the basic accessibility */ HRESULT rc = getBaseAccessible (aAccessError, true /* aCheckBusy */); if (FAILED (rc) || !aAccessError.isNull()) return rc; if (mState >= Created) { return queryInformation (&aAccessError); } aAccessError = Utf8StrFmt ("Hard disk image '%ls' is not yet created", mFilePathFull.raw()); return S_OK; } /** * Saves hard disk settings to the specified storage node and saves * all children to the specified hard disk node * * @param aHDNode or node * @param aStorageNode node */ HRESULT HVHDImage::saveSettings (settings::Key &aHDNode, settings::Key &aStorageNode) { AssertReturn (!aHDNode.isNull() && !aStorageNode.isNull(), E_FAIL); AutoReaderLock alock (this); CHECK_READY(); /* filePath (required) */ aStorageNode.setValue ("filePath", mFilePath); /* save basic settings and children */ return HardDisk::saveSettings (aHDNode); } /** * Checks if the given change of \a aOldPath to \a aNewPath affects the path * of this hard disk and updates it if necessary to reflect the new location. * Intended to be from HardDisk::updatePaths(). * * @param aOldPath old path (full) * @param aNewPath new path (full) * * @note Locks this object for writing. */ void HVHDImage::updatePath (const char *aOldPath, const char *aNewPath) { AssertReturnVoid (aOldPath); AssertReturnVoid (aNewPath); AutoLock alock (this); AssertReturnVoid (isReady()); size_t oldPathLen = strlen (aOldPath); Utf8Str path = mFilePathFull; LogFlowThisFunc (("VHD.fullPath={%s}\n", path.raw())); if (RTPathStartsWith (path, aOldPath)) { Utf8Str newPath = Utf8StrFmt ("%s%s", aNewPath, path.raw() + oldPathLen); path = newPath; mVirtualBox->calculateRelativePath (path, path); unconst (mFilePathFull) = newPath; unconst (mFilePath) = path; LogFlowThisFunc (("-> updated: full={%s} short={%s}\n", newPath.raw(), path.raw())); } } /** * Returns the string representation of this hard disk. * When \a aShort is false, returns the full image file path. * Otherwise, returns the image file name only. * * @param aShort if true, a short representation is returned * * @note Locks this object for reading. */ Bstr HVHDImage::toString (bool aShort /* = false */) { AutoReaderLock alock (this); if (!aShort) return mFilePathFull; else { Utf8Str fname = mFilePathFull; return RTPathFilename (fname.mutableRaw()); } } /** * Creates a clone of this hard disk by storing hard disk data in the given * VDI file. * * If the operation fails, @a aDeleteTarget will be set to @c true unless the * failure happened because the target file already existed. * * @param aId UUID to assign to the created image. * @param aTargetPath VDI file where the cloned image is to be to stored. * @param aProgress progress object to run during operation. * @param aDeleteTarget Whether it is recommended to delete target on * failure or not. */ HRESULT HVHDImage::cloneToImage (const Guid &aId, const Utf8Str &aTargetPath, Progress *aProgress, bool &aDeleteTarget) { ComAssertMsgFailed (("Not implemented")); return E_NOTIMPL; /// @todo will need this if we add suppord for differencing VHDs // Use code from HVirtualDiskImage::cloneToImage as an example. } /** * Creates a new differencing image for this hard disk with the given * VDI file name. * * @param aId UUID to assign to the created image * @param aTargetPath VDI file where to store the created differencing image * @param aProgress progress object to run during operation * (can be NULL) */ HRESULT HVHDImage::createDiffImage (const Guid &aId, const Utf8Str &aTargetPath, Progress *aProgress) { ComAssertMsgFailed (("Not implemented")); return E_NOTIMPL; /// @todo will need this if we add suppord for differencing VHDs // Use code from HVirtualDiskImage::createDiffImage as an example. } // private methods ///////////////////////////////////////////////////////////////////////////// /** * Helper to set a new file path. * Resolves a path relatively to the Virtual Box home directory. * * @note * Must be called from under the object's lock! */ HRESULT HVHDImage::setFilePath (const BSTR aFilePath) { if (aFilePath && *aFilePath) { /* get the full file name */ char filePathFull [RTPATH_MAX]; int vrc = RTPathAbsEx (mVirtualBox->homeDir(), Utf8Str (aFilePath), filePathFull, sizeof (filePathFull)); if (VBOX_FAILURE (vrc)) return setError (E_FAIL, tr ("Invalid image file path '%ls' (%Vrc)"), aFilePath, vrc); mFilePath = aFilePath; mFilePathFull = filePathFull; } else { mFilePath.setNull(); mFilePathFull.setNull(); } return S_OK; } /** * Helper to query information about the VDI hard disk. * * @param aAccessError not used when NULL, otherwise see #getAccessible() * * @note Must be called from under the object's lock, only after * CHECK_BUSY_AND_READERS() succeeds. */ HRESULT HVHDImage::queryInformation (Bstr *aAccessError) { AssertReturn (isLockedOnCurrentThread(), E_FAIL); /* create a lock object to completely release it later */ AutoLock alock (this); AssertReturn (mStateCheckWaiters == 0, E_FAIL); ComAssertRet (mState >= Created, E_FAIL); HRESULT rc = S_OK; int vrc = VINF_SUCCESS; /* lazily create a semaphore */ vrc = RTSemEventMultiCreate (&mStateCheckSem); ComAssertRCRet (vrc, E_FAIL); /* Reference the disk to prevent any concurrent modifications * after releasing the lock below (to unblock getters before * a lengthy operation). */ addReader(); alock.leave(); /* VBoxVHDD management interface needs to be optimized: we're opening a * file three times in a raw to get three bits of information. */ Utf8Str filePath = mFilePathFull; Bstr errMsg; /* reset any previous error report from VDError() */ mLastVDError.setNull(); do { Guid id, parentId; /// @todo changed from VD_OPEN_FLAGS_READONLY to VD_OPEN_FLAGS_NORMAL, /// because otherwise registering a VHD which so far has no UUID will /// yield a null UUID. It cannot be added to a VHD opened readonly, /// obviously. This of course changes locking behavior, but for now /// this is acceptable. A better solution needs to be found later. vrc = VDOpen (mContainer, "VHD", filePath, VD_OPEN_FLAGS_NORMAL); if (VBOX_FAILURE (vrc)) break; vrc = VDGetUuid (mContainer, 0, id.ptr()); if (VBOX_FAILURE (vrc)) break; vrc = VDGetParentUuid (mContainer, 0, parentId.ptr()); if (VBOX_FAILURE (vrc)) break; if (!mId.isEmpty()) { /* check that the actual UUID of the image matches the stored UUID */ if (mId != id) { errMsg = Utf8StrFmt ( tr ("Actual UUID {%Vuuid} of the hard disk image '%s' doesn't " "match UUID {%Vuuid} stored in the registry"), id.ptr(), filePath.raw(), mId.ptr()); break; } } else { /* assgn an UUID read from the image file */ mId = id; } if (mParent) { /* check parent UUID */ AutoLock parentLock (mParent); if (mParent->id() != parentId) { errMsg = Utf8StrFmt ( tr ("UUID {%Vuuid} of the parent image '%ls' stored in " "the hard disk image file '%s' doesn't match " "UUID {%Vuuid} stored in the registry"), parentId.raw(), mParent->toString().raw(), filePath.raw(), mParent->id().raw()); break; } } else if (!parentId.isEmpty()) { errMsg = Utf8StrFmt ( tr ("Hard disk image '%s' is a differencing image that is linked " "to a hard disk with UUID {%Vuuid} and cannot be used " "directly as a base hard disk"), filePath.raw(), parentId.raw()); break; } /* get actual file size */ /// @todo is there a direct method in RT? { RTFILE file = NIL_RTFILE; vrc = RTFileOpen (&file, filePath, RTFILE_O_READ); if (VBOX_SUCCESS (vrc)) { uint64_t size = 0; vrc = RTFileGetSize (file, &size); if (VBOX_SUCCESS (vrc)) mActualSize = size; RTFileClose (file); } if (VBOX_FAILURE (vrc)) break; } /* query logical size only for non-differencing images */ if (!mParent) { uint64_t size = VDGetSize (mContainer, 0); /* convert to MBytes */ mSize = size / 1024 / 1024; } } while (0); VDCloseAll (mContainer); /* enter the lock again */ alock.enter(); /* remove the reference */ releaseReader(); if (FAILED (rc) || VBOX_FAILURE (vrc) || !errMsg.isNull()) { LogWarningFunc (("'%ls' is not accessible " "(rc=%08X, vrc=%Vrc, errMsg='%ls')\n", mFilePathFull.raw(), rc, vrc, errMsg.raw())); if (aAccessError) { if (!errMsg.isNull()) *aAccessError = errMsg; else if (!mLastVDError.isNull()) *aAccessError = mLastVDError; else if (VBOX_FAILURE (vrc)) *aAccessError = Utf8StrFmt ( tr ("Could not access hard disk image '%ls' (%Vrc)"), mFilePathFull.raw(), vrc); } /* downgrade to not accessible */ mState = Created; } else { if (aAccessError) aAccessError->setNull(); mState = Accessible; } /* inform waiters if there are any */ if (mStateCheckWaiters > 0) { RTSemEventMultiSignal (mStateCheckSem); } else { /* delete the semaphore ourselves */ RTSemEventMultiDestroy (mStateCheckSem); mStateCheckSem = NIL_RTSEMEVENTMULTI; } /* cleanup the last error report from VDError() */ mLastVDError.setNull(); return rc; } /** * Helper to create hard disk images. * * @param aSize size in MB * @param aDynamic dynamic or fixed image * @param aProgress address of IProgress pointer to return */ HRESULT HVHDImage::createImage (ULONG64 aSize, BOOL aDynamic, IProgress **aProgress) { ComAssertMsgFailed (("Not implemented")); return E_NOTIMPL; /// @todo later // Use code from HVirtualDiskImage::createImage as an example. } /* static */ DECLCALLBACK(int) HVHDImage::VDITaskThread (RTTHREAD thread, void *pvUser) { AssertMsgFailed (("Not implemented")); return VERR_GENERAL_FAILURE; /// @todo later // Use code from HVirtualDiskImage::VDITaskThread as an example. } /* static */ DECLCALLBACK(void) HVHDImage::VDError (void *pvUser, int rc, RT_SRC_POS_DECL, const char *pszFormat, va_list va) { HVHDImage *that = static_cast (pvUser); AssertReturnVoid (that != NULL); /// @todo pass the error message to the operation initiator Utf8Str err = Utf8StrFmtVA (pszFormat, va); if (VBOX_FAILURE (rc)) err = Utf8StrFmt ("%s (%Vrc)", err.raw(), rc); if (that->mLastVDError.isNull()) that->mLastVDError = err; else that->mLastVDError = Utf8StrFmt ("%s.\n%s", that->mLastVDError.raw(), err.raw()); }