HardDiskImpl.cpp revision 2162ed290319349d7c9548c189f1730e594e1a2c
/** @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;
* 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 <algorithm>
#define CHECK_BUSY() \
do { \
if (isBusy()) \
return setError (E_UNEXPECTED, \
tr ("Hard disk '%ls' is being used by another task"), \
} while (0)
#define CHECK_BUSY_AND_READERS() \
do { \
return setError (E_UNEXPECTED, \
tr ("Hard disk '%ls' is being used by another task"), \
} while (0)
/** Task structure for asynchronous VDI operations */
struct VDITask
{
, vdi (i)
, progress (p)
{}
// for CreateDynamic, CreateStatic
// for CloneToImage
};
/**
* Progress callback handler for VDI operations.
*
* @param uPercent Completetion precentage (0-100).
* @param pvUser Pointer to the Progress instance.
*/
{
// update the progress object
if (progress)
return VINF_SUCCESS;
}
////////////////////////////////////////////////////////////////////////////////
// HardDisk class
////////////////////////////////////////////////////////////////////////////////
// constructor / destructor
////////////////////////////////////////////////////////////////////////////////
/** Shold be called by subclasses from #FinalConstruct() */
{
mRegistered = FALSE;
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!
*/
{
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.
*/
{
LogFlowMember (("HardDisk::protectedUninit()\n"));
// uninit all children
setReady (false);
if (mParent)
mParent->removeDependentChild (this);
else
{
mVirtualBox->removeDependentChild (this);
}
}
// IHardDisk properties
/////////////////////////////////////////////////////////////////////////////
{
if (!aId)
return E_POINTER;
CHECK_READY();
return S_OK;
}
{
if (!aStorageType)
return E_POINTER;
CHECK_READY();
return S_OK;
}
{
if (!aLocation)
return E_POINTER;
CHECK_READY();
return S_OK;
}
{
if (!aType)
return E_POINTER;
CHECK_READY();
return S_OK;
}
{
CHECK_READY();
if (mRegistered)
tr ("You cannot change the type of the registered hard disk '%ls'"),
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.
return S_OK;
}
{
if (!aParent)
return E_POINTER;
CHECK_READY();
return S_OK;
}
{
if (!aChildren)
return E_POINTER;
CHECK_READY();
return S_OK;
}
{
if (!aRoot)
return E_POINTER;
CHECK_READY();
return S_OK;
}
{
if (!aAccessible)
return E_POINTER;
CHECK_READY();
return rc;
return S_OK;
}
{
if (!aAllAccessible)
return E_POINTER;
CHECK_READY();
if (mParent)
{
// check the accessibility state of all ancestors
while (parent)
{
break;
if (!*aAllAccessible)
break;
}
return rc;
}
return rc;
return S_OK;
}
{
if (!aLastAccessError)
return E_POINTER;
CHECK_READY();
return S_OK;
}
{
if (!aMachineId)
return E_POINTER;
CHECK_READY();
return S_OK;
}
{
if (!aSnapshotId)
return E_POINTER;
CHECK_READY();
return S_OK;
}
{
return E_INVALIDARG;
return E_POINTER;
CHECK_READY();
CHECK_BUSY();
tr ("Cloning differencing VDI images is not yet supported ('%ls')"),
// create a project object
FALSE /* aCancelable */);
// create an imageless resulting object
// append the default path if only a name is given
{
if (!RTPathHavePath (fp))
{
}
}
// set the desired path
// ensure the directory exists
{
if (!RTDirExists (imageDir))
{
if (VBOX_FAILURE (vrc))
{
tr ("Could not create a directory '%s' "
"to store the image file (%Vrc)"),
}
}
}
// mark as busy (being created)
// (VDI task thread will unmark it)
// fill in a VDI task data
// increase readers until finished
// (VDI task thread will decrease them)
addReader();
// create the hard disk creation thread, pass operation data
(void *) task, 0, RTTHREADTYPE_MAIN_HEAVY_WORKER,
0, "VDITask");
if (VBOX_FAILURE (vrc))
{
delete task;
return E_FAIL;
}
// return interfaces to the caller
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
*/
{
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
*/
{
CHECK_READY();
if (aRegistered)
{
if (mRegistered)
tr ("Hard disk '%ls' is already registered"),
CHECK_BUSY();
}
else
{
if (!mRegistered)
tr ("Hard disk '%ls' is already unregistered"),
if (!mMachineId.isEmpty())
tr ("Hard disk '%ls' is attached to a virtual machine with UUID {%s}"),
tr ("Hard disk '%ls' has %d differencing hard disks based on it"),
}
return S_OK;
}
/**
* Checks basic accessibility of this hard disk only (w/o parents).
* Must be always called by every reimplementation in the first place.
*
* @param aAccessError on output, a null string indicates the hard disk is
* accessible, otherwise contains a message describing
* the reason of inaccessibility.
*/
{
CHECK_READY();
if (mBusy)
{
}
else
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.
*/
{
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.
}
/**
* Marks this hard disk as busy.
* A busy hard disk cannot have readers and its properties (UUID, description)
* cannot be externally modified.
*/
{
AssertReturn (isReady(), (void) 0);
AssertReturn (mBusy == false, (void) 0);
AssertReturn (mReaders == 0, (void) 0);
mBusy = true;
}
/**
* Clears the busy flag previously set by #setBusy().
*/
{
AssertReturn (isReady(), (void) 0);
AssertReturn (mBusy == true, (void) 0);
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.
*/
{
AssertReturn (isReady(), (void) 0);
AssertReturn (mBusy == false, (void) 0);
++ mReaders;
}
/**
* Decreases the number of readers of this hard disk.
*/
void HardDisk::releaseReader()
{
AssertReturn (isReady(), (void) 0);
AssertReturn (mBusy == false, (void) 0);
AssertReturn (mReaders > 0, (void) 0);
-- mReaders;
}
/**
* Increases the number of readers on all ancestors of this hard disk.
*/
void HardDisk::addReaderOnAncestors()
{
AssertReturn (isReady(), (void) 0);
if (mParent)
{
}
}
/**
* Decreases the number of readers on all ancestors of this hard disk.
*/
void HardDisk::releaseReaderOnAncestors()
{
AssertReturn (isReady(), (void) 0);
if (mParent)
{
mParent->releaseReader();
}
}
/**
* Returns true if this hard disk has children not belonging to the same
* machine.
*/
bool HardDisk::hasForeignChildren()
{
AssertReturn (isReady(), false);
// check all children
++ it)
{
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.
*/
{
++ it)
{
{
// reset the busy flag of all previous children
}
else
}
mBusy = true;
return S_OK;
}
/**
* Clears the busy flag of this hard disk and all its children.
* An opposite to #setBusyWithChildren.
*/
void HardDisk::clearBusyWithChildren()
{
AssertReturn (isReady(), (void) 0);
AssertReturn (mBusy == true, (void) 0);
++ it)
{
}
mBusy = false;
}
/**
* Checks that this hard disk and all its direct children are accessible.
*/
{
return rc;
++ it)
{
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.
*/
{
if (isDifferencing())
{
mMachineId.isEmpty());
if (mMachineId.isEmpty())
tr ("Differencing hard disk '%ls' is not associated with "
"any registered virtual machine or snapshot"),
}
{
if (mMachineId.isEmpty())
tr ("Hard disk '%ls' is not associated with any registered "
"virtual machine or snapshot, but has differencing child "
"hard disks based on it"),
}
++ it)
{
}
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!
*/
{
E_FAIL);
CHECK_READY();
// try to make the path relative to the vbox home dir
const char *filePathToRel = filePathTo;
{
}
// first ensure the directory exists
{
if (!RTDirExists (dir))
{
if (VBOX_FAILURE (vrc))
{
tr ("Could not create a directory '%s' "
"to store the image file (%Vrc)"),
}
}
}
// call storage type specific diff creation method
vdi.createObject();
TRUE /* aRegistered */);
// associate the created hard disk with the given machine
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.
*/
{
AssertReturnVoid (isReady());
/* update paths of all children */
++ it)
{
}
}
// 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
*/
{
if (!isDifferencing())
{
if (type == L"normal")
else if (type == L"immutable")
else if (type == L"writethrough")
else
E_FAIL);
}
else
return rc;
// load all children
unsigned count = 0;
{
do
{
vdi.createObject();
}
while (0);
}
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
*/
{
// uuid (required)
if (!isDifferencing())
{
// type (required)
switch (mType)
{
type = "normal";
break;
type = "immutable";
break;
type = "writethrough";
break;
}
}
// save all children
++ it)
{
do
{
}
while (0);
}
return rc;
}
////////////////////////////////////////////////////////////////////////////////
// HVirtualDiskImage class
////////////////////////////////////////////////////////////////////////////////
// constructor / destructor
////////////////////////////////////////////////////////////////////////////////
{
return rc;
mState = NotCreated;
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 aVDINod <VirtualDiskImage> node
*/
{
LogFlowMember (("HVirtualDiskImage::init (load)\n"));
do
{
/* set ready to let protectedUninit() be called on failure */
setReady (true);
/* filePath (required) */
/* load basic settings and children */
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);
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)
*/
{
LogFlowMember (("HVirtualDiskImage::init (aFilePath='%ls', aRegistered=%d)\n",
aFilePath, aRegistered));
do
{
/* set ready to let protectedUninit() be called on failure */
setReady (true);
{
/* 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. */
}
else
{
mRegistered = FALSE;
mState = NotCreated;
}
}
while (0);
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"));
if (!isReady())
return;
}
// IHardDisk properties
////////////////////////////////////////////////////////////////////////////////
{
if (!aDescription)
return E_POINTER;
CHECK_READY();
return S_OK;
}
{
CHECK_READY();
{
if (VBOX_FAILURE (vrc))
tr ("Could not change the description of the VDI hard disk '%ls' "
"(%Vrc)"),
}
return S_OK;
}
{
if (!aSize)
return E_POINTER;
CHECK_READY();
// only a non-differencing image knows the logical size
if (isDifferencing())
return S_OK;
}
{
if (!aActualSize)
return E_POINTER;
CHECK_READY();
return S_OK;
}
// IVirtualDiskImage properties
////////////////////////////////////////////////////////////////////////////////
{
if (!aFilePath)
return E_POINTER;
CHECK_READY();
return S_OK;
}
{
CHECK_READY();
if (mState != NotCreated)
return setError (E_ACCESSDENIED,
tr ("Cannot change the file path of the existing VDI hard disk '%ls'"),
// append the default path if only a name is given
{
if (!RTPathHavePath (fp))
{
}
}
return setFilePath (path);
}
{
if (!aCreated)
return E_POINTER;
CHECK_READY();
return S_OK;
}
// IVirtualDiskImage methods
/////////////////////////////////////////////////////////////////////////////
{
if (!aProgress)
return E_POINTER;
CHECK_READY();
}
{
if (!aProgress)
return E_POINTER;
CHECK_READY();
}
{
CHECK_READY();
if (mRegistered)
return setError (E_ACCESSDENIED,
tr ("Cannot delete an image of the registered hard disk image '%ls"),
mFilePathFull.raw());
if (mState == NotCreated)
tr ("Hard disk image has been already deleted or never created"));
if (VBOX_FAILURE (vrc))
mState = NotCreated;
// reset the fields
mSize = 0;
mActualSize = 0;
return S_OK;
}
/////////////////////////////////////////////////////////////////////////////
/**
* Attempts to mark the hard disk as registered.
* Only VirtualBox can call this method.
*/
{
CHECK_READY();
if (aRegistered)
{
if (mState == NotCreated)
tr ("Image file '%ls' is not yet created for this hard disk"),
mFilePathFull.raw());
if (isDifferencing())
tr ("Hard disk '%ls' is differencing and cannot be unregistered "
"explicitly"),
mFilePathFull.raw());
}
else
{
}
}
/**
* 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.
*/
{
CHECK_READY();
// check the basic accessibility
return rc;
{
return queryInformation (&aAccessError);
// if we fail here, this means something like UUID mismatch.
// Do nothing, just return the failure (error info is already
// set by queryInformation()), in hope that one of subsequent
// attempts to check for acessibility will succeed
}
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
*/
{
CHECK_READY();
// filePath (required)
// save basic settings and children
}
/**
* 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.
*/
{
AssertReturnVoid (isReady());
{
LogFlowThisFunc (("-> updated: full={%s} short={%s}\n",
}
}
/**
* 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
*/
{
if (!aShort)
return mFilePathFull;
else
{
}
}
/**
* 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
*/
{
/// @todo (dmik) cloning of differencing images is not yet supported
if (mState == NotCreated)
tr ("Source hard disk image '%s' is not yet created"),
filePathFull.raw());
addReader();
if (VBOX_SUCCESS (vrc))
if (VBOX_FAILURE (vrc))
tr ("Could not copy the hard disk image '%s' to '%s' (%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)
*/
{
addReader();
// update the UUID to correspond to the file name
if (VBOX_SUCCESS (vrc))
if (VBOX_FAILURE (vrc))
tr ("Could not create a differencing hard disk '%s' (%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!
*/
{
E_FAIL);
CHECK_READY();
// try to make the path relative to the vbox home dir
const char *filePathToRel = filePathTo;
{
}
// first ensure the directory exists
{
if (!RTDirExists (dir))
{
if (VBOX_FAILURE (vrc))
{
tr ("Could not create a directory '%s' "
"to store the image file (%Vrc)"),
}
}
}
// get modification and parent UUIDs of this image
if (VBOX_SUCCESS (vrc))
&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))
&parentUuid, &parentModUuid);
if (VBOX_FAILURE (vrc))
tr ("Could not copy the hard disk image '%s' to '%s' (%Vrc)"),
vdi.createObject();
TRUE /* aRegistered */);
return rc;
// associate the created hard disk with the given machine
return rc;
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!
*/
{
LogFlowMember (("HVirtualDiskImage::mergeImageToParent(): image='%ls'\n",
mFilePathFull.raw()));
CHECK_READY();
("non VDI storage types are not yet supported!"), E_FAIL);
parentLock.leave();
parentLock.enter();
if (VBOX_FAILURE (vrc))
tr ("Could not merge the hard disk image '%ls' to "
"its parent image '%ls' (%Vrc)"),
{
{
// reparent the child
if (mParent)
// change the parent UUID in the image as well
if (VBOX_FAILURE (vrc))
{
tr ("Could not access the hard disk image '%ls' (%Vrc)"),
break;
}
if (VBOX_FAILURE (vrc))
{
tr ("Could not update parent UUID of the hard disk image "
"'%ls' (%Vrc)"),
break;
}
}
return rc;
}
// detach all our children to avoid their uninit in #uninit()
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!
*/
{
LogFlowMember (("HVirtualDiskImage::mergeImageToChildren(): image='%ls'\n",
mFilePathFull.raw()));
CHECK_READY();
// this must be a diff image
("non VDI storage types are not yet supported!"), E_FAIL);
{
// iterate over a copy since we will modify the list
{
if (VBOX_FAILURE (vrc))
{
tr ("Could not merge the hard disk image '%ls' to "
"its parent image '%ls' (%Vrc)"),
break;
}
// reparent the child
if (mParent)
// change the parent UUID in the image as well
if (VBOX_FAILURE (vrc))
{
tr ("Could not access the hard disk image '%ls' (%Vrc)"),
break;
}
if (VBOX_FAILURE (vrc))
{
tr ("Could not update parent UUID of the hard disk image "
"'%ls' (%Vrc)"),
break;
}
// detach child to avoid its uninit in #uninit()
// remove the busy flag
}
return rc;
}
clearBusy();
return S_OK;
}
/**
* Deletes and recreates the differencing hard disk image from scratch.
* The file name and UUID remain the same.
*/
{
CHECK_READY();
("non-VDI storage types are not yet supported!"), E_FAIL);
if (VBOX_FAILURE (vrc))
tr ("Could not delete the image file '%s' (%Vrc)"),
// update the UUID to correspond to the file name
if (VBOX_SUCCESS (vrc))
if (VBOX_FAILURE (vrc))
tr ("Could not create a differencing hard disk '%s' (%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!
*/
{
{
// get the full file name
char filePathFull [RTPATH_MAX];
filePathFull, sizeof (filePathFull));
if (VBOX_FAILURE (vrc))
}
else
{
}
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!
*/
{
int vrc = VINF_SUCCESS;
/* stupid-stupid-stupid code. VBoxVHDD management is sick.
* we're opening a file three times to get three bits of information */
do
{
/* check the image file */
if (VBOX_FAILURE (vrc))
{
/* mId is empty only when constructing a HVirtualDiskImage object
* from an existing file image which UUID is not known. If we can't
* read it, we have to fail. */
tr ("Could not open the hard disk image '%s' (%Vrc)"),
break;
}
{
/* check that the actual UUID of the image matches the stored UUID */
{
tr ("Actual UUID {%Vuuid} of the hard disk image '%s' doesn't "
"match UUID {%Vuuid} stored in the registry"),
break;
}
}
else
{
/* assgn an UUID read from the image file */
}
if (mParent)
{
/* check parent UUID */
{
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"),
break;
}
}
{
tr ("Hard disk image '%s' is a differencing image and "
"cannot be opened directly"),
break;
}
{
if (VBOX_SUCCESS (vrc))
{
if (VBOX_SUCCESS (vrc))
mActualSize = size;
RTFileClose (file);
}
if (VBOX_FAILURE (vrc))
break;
}
if (!mParent)
{
/* query logical size only for non-differencing images */
if (VBOX_SUCCESS (vrc))
{
/* convert to MBytes */
}
if (VBOX_FAILURE (vrc))
break;
}
if (aAccessError)
aAccessError->setNull();
mState = Accessible;
}
while (0);
{
Log (("HVirtualDiskImage::queryInformation(): "
"WARNING: '%ls' is not accessible (%Vrc) (rc=%08X)\n",
*aAccessError =
Utf8StrFmt ("Error accessing hard disk image '%ls' (%Vrc)",
/* downgrade to not accessible */
}
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
*/
{
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
{
if (!RTDirExists (imageDir))
{
if (VBOX_FAILURE (vrc))
{
tr ("Could not create a directory '%s' "
"to store the image file (%Vrc)"),
}
}
}
// check whether the given file exists or not
if (vrc != VERR_FILE_NOT_FOUND)
{
if (VBOX_SUCCESS (vrc))
RTFileClose (file);
switch(vrc)
{
case VINF_SUCCESS:
tr ("Image file '%ls' already exists"),
mFilePathFull.raw());
default:
tr ("Invalid image file path '%ls' (%Vrc)"),
}
}
// check VDI size limits
{
return setError (E_INVALIDARG,
tr ("Invalid VDI size: %llu MB (must be in range [1, %llu] MB)"),
aSize, maxVDISize);
}
// create a project object
{
: tr ("Creating a fixed-size hard disk");
FALSE /* aCancelable */);
}
// mark as busy (being created)
// (VDI task thread will unmark it)
setBusy();
// fill in a VDI task data
: VDITask::CreateStatic,
this, progress);
// create the hard disk creation thread, pass operation data
RTTHREADTYPE_MAIN_HEAVY_WORKER, 0, "VDITask");
if (VBOX_FAILURE (vrc))
{
clearBusy();
delete task;
}
else
{
// get one interface for the caller
}
return rc;
}
// static
{
LogFlow (("vdiTaskThread(): operation=%d, size=%llu\n",
{
case VDITask::CloneToImage: break;
default: AssertFailedReturn (VERR_GENERAL_FAILURE); break;
}
{
/// @todo (dmik) check locks
// release reader added in HardDisk::CloneToImage()
}
else
{
{
// we have a non-null UUID, update the created image
}
if (VBOX_FAILURE (vrc))
{
errorMsg = Utf8StrFmt (
tr ("Falied to create a hard disk image '%ls' (%Vrc)"),
}
}
// clear busy set in in HardDisk::CloneToImage() or
// in HVirtualDiskImage::createImage()
{
// update VDI data fields
// complete the progress object
}
else
{
/* delete the target file so we don't have orphaned files */
// complete the progress object
if (errorMsg)
errorMsg);
else
}
delete task;
return VINF_SUCCESS;
}
////////////////////////////////////////////////////////////////////////////////
// HISCSIHardDisk class
////////////////////////////////////////////////////////////////////////////////
// constructor / destructor
////////////////////////////////////////////////////////////////////////////////
{
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
*/
{
LogFlowMember (("HISCSIHardDisk::init (load)\n"));
do
{
// set ready to let protectedUninit() be called on failure
setReady (true);
// server (required)
// target (required)
// port (optional)
// lun (optional)
// userName (optional)
// password (optional)
LogFlowMember ((" 'iscsi:%ls:%hu@%ls/%ls:%llu'\n",
mLun));
// load basic settings and children
{
tr ("Currently, non-Writethrough iSCSI hard disks are not "
"allowed ('%ls')"),
break;
}
mRegistered = TRUE;
}
while (0);
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.
*/
{
LogFlowMember (("HISCSIHardDisk::init()\n"));
do
{
// set ready to let protectedUninit() be called on failure
setReady (true);
// we have to generate a new UUID
mRegistered = FALSE;
}
while (0);
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"));
if (!isReady())
return;
}
// IHardDisk properties
////////////////////////////////////////////////////////////////////////////////
{
if (!aDescription)
return E_POINTER;
CHECK_READY();
return S_OK;
}
{
CHECK_READY();
if (mDescription != aDescription)
{
if (mRegistered)
return mVirtualBox->saveSettings();
}
return S_OK;
}
{
if (!aSize)
return E_POINTER;
CHECK_READY();
return S_OK;
}
{
if (!aActualSize)
return E_POINTER;
CHECK_READY();
return S_OK;
}
// IISCSIHardDisk properties
////////////////////////////////////////////////////////////////////////////////
{
if (!aServer)
return E_POINTER;
CHECK_READY();
return S_OK;
}
{
return E_INVALIDARG;
CHECK_READY();
{
if (mRegistered)
return mVirtualBox->saveSettings();
}
return S_OK;
}
{
if (!aPort)
return E_POINTER;
CHECK_READY();
return S_OK;
}
{
CHECK_READY();
{
if (mRegistered)
return mVirtualBox->saveSettings();
}
return S_OK;
}
{
if (!aTarget)
return E_POINTER;
CHECK_READY();
return S_OK;
}
{
return E_INVALIDARG;
CHECK_READY();
{
if (mRegistered)
return mVirtualBox->saveSettings();
}
return S_OK;
}
{
if (!aLun)
return E_POINTER;
CHECK_READY();
return S_OK;
}
{
CHECK_READY();
{
if (mRegistered)
return mVirtualBox->saveSettings();
}
return S_OK;
}
{
if (!aUserName)
return E_POINTER;
CHECK_READY();
return S_OK;
}
{
CHECK_READY();
{
if (mRegistered)
return mVirtualBox->saveSettings();
}
return S_OK;
}
{
if (!aPassword)
return E_POINTER;
CHECK_READY();
return S_OK;
}
{
CHECK_READY();
{
if (mRegistered)
return mVirtualBox->saveSettings();
}
return S_OK;
}
/////////////////////////////////////////////////////////////////////////////
/**
* Attempts to mark the hard disk as registered.
* Only VirtualBox can call this method.
*/
{
CHECK_READY();
if (aRegistered)
{
tr ("iSCSI Hard disk has no server or target defined"));
}
else
{
}
}
/**
* Checks accessibility of this iSCSI hard disk.
*/
{
CHECK_READY();
// check the basic accessibility
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
*/
{
CHECK_READY();
// server (required)
// target (required)
// port (optional)
if (mPort != 0)
else
// lun (optional)
if (mLun != 0)
else
// userName (optional)
else
// password (optional)
else
// save basic settings and children
}
/**
* 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
*/
{
if (!aShort)
{
/* the format is iscsi:[<user@>]<server>[:<port>]/<target>[:<lun>] */
}
else
{
}
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
*/
{
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)
*/
{
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!
*/
{
/// @todo (dmik) query info about this iSCSI disk,
// set mSize and mActualSize,
// or set aAccessError in case of failure
return S_OK;
}