HardDiskImpl.cpp revision a0240ff4f7663045c848fdbc192ea3d4d9f70a11
/** @file
*
* VirtualBox COM class implementation
*/
/*
* Copyright (C) 2006 InnoTek Systemberatung GmbH
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* you can redistribute it and/or modify it under the terms of the GNU
* General Public License as published by the Free Software Foundation,
* in version 2 as it comes in the "COPYING" file of the VirtualBox OSE
* distribution. VirtualBox OSE is distributed in the hope that it will
* be useful, but WITHOUT ANY WARRANTY of any kind.
*
* If you received this file as part of a commercial VirtualBox
* distribution, then only the terms of your commercial VirtualBox
* license agreement apply instead of the previous paragraph.
*/
#include "HardDiskImpl.h"
#include "ProgressImpl.h"
#include "VirtualBoxImpl.h"
#include "SystemPropertiesImpl.h"
#include "Logging.h"
#include <iprt/string.h>
#include <iprt/thread.h>
#include <iprt/file.h>
#include <iprt/path.h>
#include <iprt/dir.h>
#include <VBox/VBoxHDD.h>
#include <VBox/VBoxHDD-new.h>
#include <VBox/err.h>
#include <algorithm>
#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 <HVirtualDiskImage> vdi;
ComObjPtr <Progress> progress;
/* for CreateDynamic, CreateStatic */
uint64_t size;
/* for CloneToImage */
ComObjPtr <HardDisk> 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 <Progress *> (pvUser);
/* update the progress object */
if (progress)
progress->notifyProgress (uPercent);
return VINF_SUCCESS;
}
////////////////////////////////////////////////////////////////////////////////
// HardDisk class
////////////////////////////////////////////////////////////////////////////////
// constructor / destructor
////////////////////////////////////////////////////////////////////////////////
/** Shold be called by subclasses from #FinalConstruct() */
HRESULT HardDisk::FinalConstruct()
{
mRegistered = FALSE;
mStorageType = HardDiskStorageType_VirtualDiskImage;
mType = HardDiskType_NormalHardDisk;
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)
{
LogFlowMember (("HardDisk::protectedInit (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)
{
LogFlowMember (("HardDisk::protectedUninit()\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;
AutoLock alock (this);
CHECK_READY();
mId.cloneTo (aId);
return S_OK;
}
STDMETHODIMP HardDisk::COMGETTER(StorageType) (HardDiskStorageType_T *aStorageType)
{
if (!aStorageType)
return E_POINTER;
AutoLock alock (this);
CHECK_READY();
*aStorageType = mStorageType;
return S_OK;
}
STDMETHODIMP HardDisk::COMGETTER(Location) (BSTR *aLocation)
{
if (!aLocation)
return E_POINTER;
AutoLock alock (this);
CHECK_READY();
toString (false /* aShort */).cloneTo (aLocation);
return S_OK;
}
STDMETHODIMP HardDisk::COMGETTER(Type) (HardDiskType_T *aType)
{
if (!aType)
return E_POINTER;
AutoLock 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());
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;
AutoLock alock (this);
CHECK_READY();
mParent.queryInterfaceTo (aParent);
return S_OK;
}
STDMETHODIMP HardDisk::COMGETTER(Children) (IHardDiskCollection **aChildren)
{
if (!aChildren)
return E_POINTER;
AutoLock lock(this);
CHECK_READY();
AutoLock chLock (childrenLock());
ComObjPtr <HardDiskCollection> collection;
collection.createObject();
collection->init (children());
collection.queryInterfaceTo (aChildren);
return S_OK;
}
STDMETHODIMP HardDisk::COMGETTER(Root) (IHardDisk **aRoot)
{
if (!aRoot)
return E_POINTER;
AutoLock 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 <HardDisk> 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;
AutoLock alock (this);
CHECK_READY();
mLastAccessError.cloneTo (aLastAccessError);
return S_OK;
}
STDMETHODIMP HardDisk::COMGETTER(MachineId) (GUIDPARAMOUT aMachineId)
{
if (!aMachineId)
return E_POINTER;
AutoLock alock (this);
CHECK_READY();
mMachineId.cloneTo (aMachineId);
return S_OK;
}
STDMETHODIMP HardDisk::COMGETTER(SnapshotId) (GUIDPARAMOUT aSnapshotId)
{
if (!aSnapshotId)
return E_POINTER;
AutoLock 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;
progress.createObject();
rc = progress->init (mVirtualBox, (IVirtualDiskImage *) this,
Bstr (tr ("Creating a hard disk clone")),
FALSE /* aCancelable */);
CheckComRCReturnRC (rc);
/* create an imageless resulting object */
ComObjPtr <HVirtualDiskImage> 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> HardDisk::root() const
{
ComObjPtr <HardDisk> root = const_cast <HardDisk *> (this);
ComObjPtr <HardDisk> 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 */)
{
AutoLock 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)
{
AutoLock 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()
{
AutoLock alock (this);
AssertReturn (isReady(), false);
AssertReturn (!mMachineId.isEmpty(), false);
/* check all children */
AutoLock chLock (childrenLock());
for (HardDiskList::const_iterator it = children().begin();
it != children().end();
++ it)
{
ComObjPtr <HardDisk> child = *it;
AutoLock 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());
AutoLock chLock (childrenLock());
for (HardDiskList::const_iterator it = children().begin();
it != children().end();
++ it)
{
ComObjPtr <HardDisk> 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);
AutoLock chLock (childrenLock());
for (HardDiskList::const_iterator it = children().begin();
it != children().end();
++ it)
{
ComObjPtr <HardDisk> 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.
*/
HRESULT HardDisk::getAccessibleWithChildren (Bstr &aAccessError)
{
AutoLock alock (this);
AssertReturn (isReady(), E_FAIL);
HRESULT rc = getAccessible (aAccessError);
if (FAILED (rc) || !aAccessError.isNull())
return rc;
AutoLock chLock (childrenLock());
for (HardDiskList::const_iterator it = children().begin();
it != children().end();
++ it)
{
ComObjPtr <HardDisk> 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()
{
AutoLock 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;
AutoLock chLock (childrenLock());
if (mParent.isNull() && mType == HardDiskType_NormalHardDisk &&
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 <HVirtualDiskImage> &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 <HVirtualDiskImage> 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 */
AutoLock 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> &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);
HRESULT rc = S_OK;
/* try to guess the probe order by extension */
Utf8Str loc = aLocation;
char *ext = RTPathExt (loc);
HardDiskStorageType_T order [2];
if (RTPathCompare (ext, ".vmdk") == 0)
{
order [0] = HardDiskStorageType_VMDKImage;
order [1] = HardDiskStorageType_VirtualDiskImage;
}
else
{
order [0] = HardDiskStorageType_VirtualDiskImage;
order [1] = HardDiskStorageType_VMDKImage;
}
for (size_t i = 0; i < ELEMENTS (order); ++ i)
{
switch (order [i])
{
case HardDiskStorageType_VirtualDiskImage:
{
ComObjPtr <HVirtualDiskImage> obj;
obj.createObject();
rc = obj->init (aVirtualBox, NULL, aLocation,
FALSE /* aRegistered */);
if (SUCCEEDED (rc))
{
hardDisk = obj;
return rc;
}
break;
}
case HardDiskStorageType_VMDKImage:
{
ComObjPtr <HVMDKImage> obj;
obj.createObject();
rc = obj->init (aVirtualBox, NULL, aLocation,
FALSE /* aRegistered */);
if (SUCCEEDED (rc))
{
hardDisk = obj;
return rc;
}
break;
}
default:
{
ComAssertComRCRetRC (E_FAIL);
}
}
}
return rc;
}
// 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 <HardDisk> node when #isDifferencing() = false, or
* <DiffHardDisk> node otherwise
*
* @note
* Must be called from under the object's lock
*/
HRESULT HardDisk::loadSettings (CFGNODE aHDNode)
{
AssertReturn (aHDNode, E_FAIL);
Guid uuid; /* uuid (required) */
CFGLDRQueryUUID (aHDNode, "uuid", uuid.ptr());
mId = uuid;
if (!isDifferencing())
{
Bstr type; /* type (required for <HardDisk> nodes only) */
CFGLDRQueryBSTR (aHDNode, "type", type.asOutParam());
if (type == L"normal")
mType = HardDiskType_NormalHardDisk;
else if (type == L"immutable")
mType = HardDiskType_ImmutableHardDisk;
else if (type == L"writethrough")
mType = HardDiskType_WritethroughHardDisk;
else
ComAssertMsgFailedRet (("Invalid hard disk type '%ls'\n", type.raw()),
E_FAIL);
}
else
mType = HardDiskType_NormalHardDisk;
HRESULT rc = mVirtualBox->registerHardDisk (this, VirtualBox::RHD_OnStartUp);
if (FAILED (rc))
return rc;
/* load all children */
unsigned count = 0;
CFGLDRCountChildren (aHDNode, "DiffHardDisk", &count);
for (unsigned i = 0; i < count && SUCCEEDED (rc); ++ i)
{
CFGNODE hdNode = 0;
CFGLDRGetChildNode (aHDNode, "DiffHardDisk", i, &hdNode);
ComAssertBreak (hdNode, rc = E_FAIL);
do
{
CFGNODE vdiNode = 0;
CFGLDRGetChildNode (hdNode, "VirtualDiskImage", 0, &vdiNode);
ComAssertBreak (vdiNode, rc = E_FAIL);
ComObjPtr <HVirtualDiskImage> vdi;
vdi.createObject();
rc = vdi->init (mVirtualBox, this, hdNode, vdiNode);
CFGLDRReleaseNode (vdiNode);
}
while (0);
CFGLDRReleaseNode (hdNode);
}
return rc;
}
/**
* Saves the base settings of the hard disk to the given node
* and saves all child hard disks as <DiffHardDisk> nodes.
*
* Subclasses must call this method in their saveSettings() methods
* in order to be properly saved to the settings file.
*
* @param aHDNode <HardDisk> node when #isDifferencing() = false, or
* <DiffHardDisk> node otherwise
*
* @note
* Must be called from under the object's lock
*/
HRESULT HardDisk::saveSettings (CFGNODE aHDNode)
{
AssertReturn (aHDNode, E_FAIL);
/* uuid (required) */
CFGLDRSetUUID (aHDNode, "uuid", mId.ptr());
if (!isDifferencing())
{
/* type (required) */
const char *type = NULL;
switch (mType)
{
case HardDiskType_NormalHardDisk:
type = "normal";
break;
case HardDiskType_ImmutableHardDisk:
type = "immutable";
break;
case HardDiskType_WritethroughHardDisk:
type = "writethrough";
break;
}
CFGLDRSetString (aHDNode, "type", type);
}
HRESULT rc = S_OK;
/* save all children */
AutoLock chLock (childrenLock());
for (HardDiskList::const_iterator it = children().begin();
it != children().end() && SUCCEEDED (rc);
++ it)
{
ComObjPtr <HardDisk> child = *it;
AutoLock childLock (child);
CFGNODE hdNode = 0;
CFGLDRAppendChildNode (aHDNode, "DiffHardDisk", &hdNode);
ComAssertBreak (hdNode, rc = E_FAIL);
do
{
CFGNODE vdiNode = 0;
CFGLDRAppendChildNode (hdNode, "VirtualDiskImage", &vdiNode);
ComAssertBreak (vdiNode, rc = E_FAIL);
rc = child->saveSettings (hdNode, vdiNode);
CFGLDRReleaseNode (vdiNode);
}
while (0);
CFGLDRReleaseNode (hdNode);
}
return rc;
}
////////////////////////////////////////////////////////////////////////////////
// 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 <HardDisk> node
* @param aVDINode <VirtualDiskImage> node
*/
HRESULT HVirtualDiskImage::init (VirtualBox *aVirtualBox, HardDisk *aParent,
CFGNODE aHDNode, CFGNODE aVDINode)
{
LogFlowMember (("HVirtualDiskImage::init (load)\n"));
AssertReturn (aHDNode && aVDINode, 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;
CFGLDRQueryBSTR (aVDINode, "filePath", filePath.asOutParam());
rc = setFilePath (filePath);
CheckComRCBreakRC (rc);
LogFlowMember ((" '%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 aFilePath path to the image file (can be NULL to create an
* imageless object)
* @param aRegistered whether to mark this disk as registered or not
* (ignored when \a aFilePath is NULL, assuming FALSE)
*/
HRESULT HVirtualDiskImage::init (VirtualBox *aVirtualBox, HardDisk *aParent,
const BSTR aFilePath, BOOL aRegistered /* = FALSE */)
{
LogFlowMember (("HVirtualDiskImage::init (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()
{
LogFlowMember (("HVirtualDiskImage::uninit()\n"));
AutoLock alock (this);
if (!isReady())
return;
HardDisk::protectedUninit (alock);
}
// IHardDisk properties
////////////////////////////////////////////////////////////////////////////////
STDMETHODIMP HVirtualDiskImage::COMGETTER(Description) (BSTR *aDescription)
{
if (!aDescription)
return E_POINTER;
AutoLock 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;
AutoLock 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;
AutoLock alock (this);
CHECK_READY();
*aActualSize = mActualSize;
return S_OK;
}
// IVirtualDiskImage properties
////////////////////////////////////////////////////////////////////////////////
STDMETHODIMP HVirtualDiskImage::COMGETTER(FilePath) (BSTR *aFilePath)
{
if (!aFilePath)
return E_POINTER;
AutoLock 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;
AutoLock 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"));
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 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.
*/
HRESULT HVirtualDiskImage::getAccessible (Bstr &aAccessError)
{
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 <HardDisk> or <DiffHardDisk> node
* @param aStorageNode <VirtualDiskImage> node
*/
HRESULT HVirtualDiskImage::saveSettings (CFGNODE aHDNode, CFGNODE aStorageNode)
{
AssertReturn (aHDNode && aStorageNode, E_FAIL);
AutoLock alock (this);
CHECK_READY();
// filePath (required)
CFGLDRSetBSTR (aStorageNode, "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
*/
Bstr HVirtualDiskImage::toString (bool aShort /* = false */)
{
AutoLock 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 name.
*
* @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
*/
HRESULT
HVirtualDiskImage::cloneToImage (const Guid &aId, const Utf8Str &aTargetPath,
Progress *aProgress)
{
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 <Progress *> (aProgress));
alock.enter();
releaseReader();
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 <Progress *> (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 <HVirtualDiskImage> &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 <Progress *> (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 <HVirtualDiskImage> 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)
{
LogFlowMember (("HVirtualDiskImage::mergeImageToParent(): image='%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 <Progress *> (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;
AutoLock chLock (childrenLock());
for (HardDiskList::const_iterator it = children().begin();
it != children().end(); ++ it)
{
ComObjPtr <HVirtualDiskImage> 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)
{
LogFlowMember (("HVirtualDiskImage::mergeImageToChildren(): image='%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 <HardDisk> hd = *it;
ComObjPtr <HVirtualDiskImage> 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 <Progress *> (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;
}
// 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 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 <ISystemProperties> 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;
progress.createObject();
{
Bstr desc = aDynamic ? tr ("Creating a dynamically expanding hard disk")
: tr ("Creating a fixed-size hard disk");
rc = progress->init (mVirtualBox, (IVirtualDiskImage *) 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 <VDITask *> (pvUser);
AssertReturn (task, VERR_GENERAL_FAILURE);
LogFlow (("vdiTaskThread(): 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;
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);
/* 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 <Progress *> (task->progress));
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;
}
}
LogFlow (("vdiTaskThread(): 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 */
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 <HardDisk> node
* @param aVDINod <ISCSIHardDisk> node
*/
HRESULT HISCSIHardDisk::init (VirtualBox *aVirtualBox,
CFGNODE aHDNode, CFGNODE aISCSINode)
{
LogFlowMember (("HISCSIHardDisk::init (load)\n"));
AssertReturn (aHDNode && aISCSINode, 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) */
CFGLDRQueryBSTR (aISCSINode, "server", mServer.asOutParam());
/* target (required) */
CFGLDRQueryBSTR (aISCSINode, "target", mTarget.asOutParam());
/* port (optional) */
CFGLDRQueryUInt16 (aISCSINode, "port", &mPort);
/* lun (optional) */
CFGLDRQueryUInt64 (aISCSINode, "lun", &mLun);
/* userName (optional) */
CFGLDRQueryBSTR (aISCSINode, "userName", mUserName.asOutParam());
/* password (optional) */
CFGLDRQueryBSTR (aISCSINode, "password", mPassword.asOutParam());
LogFlowMember ((" '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_WritethroughHardDisk)
{
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)
{
LogFlowMember (("HISCSIHardDisk::init()\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_WritethroughHardDisk;
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()
{
LogFlowMember (("HISCSIHardDisk::uninit()\n"));
AutoLock alock (this);
if (!isReady())
return;
HardDisk::protectedUninit (alock);
}
// IHardDisk properties
////////////////////////////////////////////////////////////////////////////////
STDMETHODIMP HISCSIHardDisk::COMGETTER(Description) (BSTR *aDescription)
{
if (!aDescription)
return E_POINTER;
AutoLock 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;
AutoLock alock (this);
CHECK_READY();
*aSize = mSize;
return S_OK;
}
STDMETHODIMP HISCSIHardDisk::COMGETTER(ActualSize) (ULONG64 *aActualSize)
{
if (!aActualSize)
return E_POINTER;
AutoLock alock (this);
CHECK_READY();
*aActualSize = mActualSize;
return S_OK;
}
// IISCSIHardDisk properties
////////////////////////////////////////////////////////////////////////////////
STDMETHODIMP HISCSIHardDisk::COMGETTER(Server) (BSTR *aServer)
{
if (!aServer)
return E_POINTER;
AutoLock 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;
AutoLock 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;
AutoLock 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;
AutoLock 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;
AutoLock 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;
AutoLock 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.
*/
HRESULT HISCSIHardDisk::getAccessible (Bstr &aAccessError)
{
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 <HardDisk>
* @param aStorageNode <ISCSIHardDisk> node
*/
HRESULT HISCSIHardDisk::saveSettings (CFGNODE aHDNode, CFGNODE aStorageNode)
{
AssertReturn (aHDNode && aStorageNode, E_FAIL);
AutoLock alock (this);
CHECK_READY();
/* server (required) */
CFGLDRSetBSTR (aStorageNode, "server", mServer);
/* target (required) */
CFGLDRSetBSTR (aStorageNode, "target", mTarget);
/* port (optional) */
if (mPort != 0)
CFGLDRSetUInt16 (aStorageNode, "port", mPort);
else
CFGLDRDeleteAttribute (aStorageNode, "port");
/* lun (optional) */
if (mLun != 0)
CFGLDRSetUInt64 (aStorageNode, "lun", mLun);
else
CFGLDRDeleteAttribute (aStorageNode, "lun");
/* userName (optional) */
if (!mUserName.isNull())
CFGLDRSetBSTR (aStorageNode, "userName", mUserName);
else
CFGLDRDeleteAttribute (aStorageNode, "userName");
/* password (optional) */
if (!mPassword.isNull())
CFGLDRSetBSTR (aStorageNode, "password", mPassword);
else
CFGLDRDeleteAttribute (aStorageNode, "password");
/* 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
*/
Bstr HISCSIHardDisk::toString (bool aShort /* = false */)
{
AutoLock alock (this);
Bstr str;
if (!aShort)
{
/* the format is iscsi:[<user@>]<server>[:<port>]/<target>[:<lun>] */
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 name.
*
* @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
*/
HRESULT
HISCSIHardDisk::cloneToImage (const Guid &aId, const Utf8Str &aTargetPath,
Progress *aProgress)
{
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 lock!
*/
HRESULT HISCSIHardDisk::queryInformation (Bstr &aAccessError)
{
/// @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;
return S_OK;
}
void HVMDKImage::FinalRelease()
{
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 <HardDisk> node
* @param aVMDKNode <VirtualDiskImage> node
*/
HRESULT HVMDKImage::init (VirtualBox *aVirtualBox, HardDisk *aParent,
CFGNODE aHDNode, CFGNODE aVMDKNode)
{
LogFlowMember (("HVMDKImage::init (load)\n"));
AssertReturn (aHDNode && aVMDKNode, 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;
CFGLDRQueryBSTR (aVMDKNode, "filePath", filePath.asOutParam());
rc = setFilePath (filePath);
CheckComRCBreakRC (rc);
LogFlowMember ((" '%ls'\n", mFilePathFull.raw()));
/* load basic settings and children */
rc = loadSettings (aHDNode);
CheckComRCBreakRC (rc);
if (mType != HardDiskType_WritethroughHardDisk)
{
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 NULL.
* @param aFilePath path to the image file (can be NULL to create an
* imageless object)
* @param aRegistered whether to mark this disk as registered or not
* (ignored when \a aFilePath is NULL, assuming FALSE)
*/
HRESULT HVMDKImage::init (VirtualBox *aVirtualBox, HardDisk *aParent,
const BSTR aFilePath, BOOL aRegistered /* = FALSE */)
{
LogFlowMember (("HVMDKImage::init (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_WritethroughHardDisk;
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()
{
LogFlowMember (("HVMDKImage::uninit()\n"));
AutoLock alock (this);
if (!isReady())
return;
HardDisk::protectedUninit (alock);
}
// IHardDisk properties
////////////////////////////////////////////////////////////////////////////////
STDMETHODIMP HVMDKImage::COMGETTER(Description) (BSTR *aDescription)
{
if (!aDescription)
return E_POINTER;
AutoLock 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;
AutoLock alock (this);
CHECK_READY();
/// @todo (r=dmik) 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 HVMDKImage::COMGETTER(ActualSize) (ULONG64 *aActualSize)
{
if (!aActualSize)
return E_POINTER;
AutoLock alock (this);
CHECK_READY();
*aActualSize = mActualSize;
return S_OK;
}
// IVirtualDiskImage properties
////////////////////////////////////////////////////////////////////////////////
STDMETHODIMP HVMDKImage::COMGETTER(FilePath) (BSTR *aFilePath)
{
if (!aFilePath)
return E_POINTER;
AutoLock 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;
AutoLock 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 suppord 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.
*/
HRESULT HVMDKImage::getAccessible (Bstr &aAccessError)
{
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 <HardDisk> or <DiffHardDisk> node
* @param aStorageNode <VirtualDiskImage> node
*/
HRESULT HVMDKImage::saveSettings (CFGNODE aHDNode, CFGNODE aStorageNode)
{
AssertReturn (aHDNode && aStorageNode, E_FAIL);
AutoLock alock (this);
CHECK_READY();
/* filePath (required) */
CFGLDRSetBSTR (aStorageNode, "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
*/
Bstr HVMDKImage::toString (bool aShort /* = false */)
{
AutoLock 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 name.
*
* @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
*/
HRESULT
HVMDKImage::cloneToImage (const Guid &aId, const Utf8Str &aTargetPath,
Progress *aProgress)
{
ComAssertMsgFailed (("Not implemented"));
return E_NOTIMPL;
/// @todo (r=dmik) will need this if we add suppord 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 suppord 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;
do
{
/// @todo remove when the code below is implemented
if (mId.isEmpty())
mId.create();
/// @todo implement
//
// /* 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 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.
}