MediumImpl.cpp revision 8111ba644e5ae3427f63219e340c891a0edf35f3
/* $Id$ */
/** @file
* VirtualBox COM class implementation
*/
/*
* Copyright (C) 2008-2010 Oracle Corporation
*
* 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 (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*/
#include "MediumImpl.h"
#include "ProgressImpl.h"
#include "SystemPropertiesImpl.h"
#include "VirtualBoxImpl.h"
#include "AutoCaller.h"
#include "Logging.h"
#include "VBox/com/MultiResult.h"
#include "VBox/com/ErrorInfo.h"
#include <VBox/settings.h>
#include <algorithm>
////////////////////////////////////////////////////////////////////////////////
//
// Medium data definition
//
////////////////////////////////////////////////////////////////////////////////
/** Describes how a machine refers to this medium. */
struct BackRef
{
/** Equality predicate for stdc++. */
{
bool operator()(const argument_type &aThat) const
{
}
};
: machineId(aMachineId),
{
if (!aSnapshotId.isEmpty())
}
bool fInCurState : 1;
};
{
Data()
: pVirtualBox(NULL),
size(0),
readers(0),
queryInfoRunning(false),
logicalSize(0),
autoReset(false),
setImageId(false),
setParentId(false),
hostDrive(false),
implicit(false),
{}
/** weak VirtualBox parent */
VirtualBox * const pVirtualBox;
// pParent and llChildren are protected by VirtualBox::getMediaTreeLockHandle()
MediaList llChildren; // to add a child, just call push_back; to remove a child, call child->deparent() which does a lookup
bool queryInfoRunning : 1;
bool autoReset : 1;
/** the following members are invalid after changing UUID on open */
bool setImageId : 1;
bool setParentId : 1;
bool hostDrive : 1;
bool implicit : 1;
};
typedef struct VDSOCKETINT
{
/** Socket handle. */
} VDSOCKETINT, *PVDSOCKETINT;
////////////////////////////////////////////////////////////////////////////////
//
// Globals
//
////////////////////////////////////////////////////////////////////////////////
/**
* Medium::Task class for asynchronous operations.
*
* @note Instances of this class must be created using new() because the
* task thread function will delete them when the task is complete.
*
* @note The constructor of this class adds a caller on the managed Medium
* object which is automatically released upon destruction.
*/
{
public:
{
return;
/* Set up a per-operation progress interface, can be used freely (for
* binary operations you can use it either on the source or target). */
"Medium::Task::vdInterfaceProgress",
if (RT_FAILURE(vrc))
}
// Make all destructors virtual. Just in case.
virtual ~Task()
{}
// Whether the caller needs to call VirtualBox::saveSettings() after
// the task function returns. Only used in synchronous (wait) mode;
// otherwise the task will save the settings itself.
bool *m_pfNeedsSaveSettings;
protected:
private:
};
{
public:
{}
private:
};
{
public:
bool fKeepMediumLockList = false)
{
return;
}
{
if (!mfKeepMediumLockList && mpMediumLockList)
delete mpMediumLockList;
}
private:
bool mfKeepMediumLockList;
};
{
public:
bool fKeepSourceMediumLockList = false,
bool fKeepTargetMediumLockList = false)
{
return;
/* aParent may be NULL */
return;
}
~CloneTask()
{
delete mpSourceMediumLockList;
delete mpTargetMediumLockList;
}
private:
};
{
public:
bool fKeepMediumLockList = false)
{
}
~CompactTask()
{
if (!mfKeepMediumLockList && mpMediumLockList)
delete mpMediumLockList;
}
private:
bool mfKeepMediumLockList;
};
{
public:
bool fKeepMediumLockList = false)
{}
~ResetTask()
{
if (!mfKeepMediumLockList && mpMediumLockList)
delete mpMediumLockList;
}
private:
bool mfKeepMediumLockList;
};
{
public:
bool fKeepMediumLockList = false)
{}
~DeleteTask()
{
if (!mfKeepMediumLockList && mpMediumLockList)
delete mpMediumLockList;
}
private:
bool mfKeepMediumLockList;
};
{
public:
bool fMergeForward,
const MediaList &aChildrenToReparent,
bool fKeepMediumLockList = false)
mfChildrenCaller(false),
{
++it)
{
{
--it2)
{
(*it2)->releaseCaller();
}
return;
}
}
mfChildrenCaller = true;
}
~MergeTask()
{
if (!mfKeepMediumLockList && mpMediumLockList)
delete mpMediumLockList;
if (mfChildrenCaller)
{
++it)
{
(*it)->releaseCaller();
}
}
}
bool mfMergeForward;
/* When mChildrenToReparent is empty then mParentForTarget is non-null.
* In other words: they are used in different cases. */
private:
bool mfChildrenCaller;
bool mfKeepMediumLockList;
};
/**
* Thread function for time-consuming medium tasks.
*
* @param pvUser Pointer to the Medium::Task instance.
*/
/* static */
{
/* complete the progress if run asynchronously */
{
}
/* pTask is no longer needed, delete it. */
delete pTask;
return (int)rc;
}
/**
* PFNVDPROGRESS callback handler for Task operations.
*
* @param pvUser Pointer to the Progress instance.
* @param uPercent Completetion precentage (0-100).
*/
/*static*/
{
{
/* update the progress object, capping it at 99% as the final percent
* is used for additional operations like setting the UUIDs and similar. */
{
return VERR_CANCELLED;
else
return VERR_INVALID_STATE;
}
}
return VINF_SUCCESS;
}
/**
* Implementation code for the "create base" task.
*/
{
return mMedium->taskCreateBaseHandler(*this);
}
/**
* Implementation code for the "create diff" task.
*/
{
return mMedium->taskCreateDiffHandler(*this);
}
/**
* Implementation code for the "clone" task.
*/
{
return mMedium->taskCloneHandler(*this);
}
/**
* Implementation code for the "compact" task.
*/
{
return mMedium->taskCompactHandler(*this);
}
/**
* Implementation code for the "reset" task.
*/
{
return mMedium->taskResetHandler(*this);
}
/**
* Implementation code for the "delete" task.
*/
{
return mMedium->taskDeleteHandler(*this);
}
/**
* Implementation code for the "merge" task.
*/
{
return mMedium->taskMergeHandler(*this);
}
////////////////////////////////////////////////////////////////////////////////
//
// Medium constructor / destructor
//
////////////////////////////////////////////////////////////////////////////////
{
m = new Data;
/* Initialize the callbacks of the VD error interface */
/* Initialize the callbacks of the VD config interface */
/* Initialize the callbacks of the VD TCP interface (we always use the host
* IP stack for now) */
/* Initialize the per-disk interface chain */
int vrc;
"Medium::vdInterfaceError",
&m->vdIfCallsError, this, &m->vdDiskIfaces);
"Medium::vdInterfaceConfig",
&m->vdIfCallsConfig, this, &m->vdDiskIfaces);
"Medium::vdInterfaceTcpNet",
&m->vdIfCallsTcpNet, this, &m->vdDiskIfaces);
return S_OK;
}
void Medium::FinalRelease()
{
uninit();
delete m;
}
/**
* Initializes an empty hard disk object without creating or opening an associated
* storage unit.
*
* This gets called by VirtualBox::CreateHardDisk().
*
* For hard disks that don't have the VD_CAP_CREATE_FIXED or
* VD_CAP_CREATE_DYNAMIC capability (and therefore cannot be created or deleted
* with the means of VirtualBox) the associated storage unit is assumed to be
* ready for use so the state of the hard disk object will be set to Created.
*
* @param aVirtualBox VirtualBox object.
* @param aLocation Storage unit location.
* @param pfNeedsSaveSettings Optional pointer to a bool that must have been initialized to false and that will be set to true
* by this function if the caller should invoke VirtualBox::saveSettings() because the global settings have changed.
*/
bool *pfNeedsSaveSettings)
{
/* Enclose the state transition NotReady->InInit->Ready */
AutoInitSpan autoInitSpan(this);
/* share VirtualBox weakly (parent remains NULL so far) */
/* no storage yet */
m->state = MediumState_NotCreated;
/* cannot be a host drive */
m->hostDrive = false;
/* No storage unit is created yet, no need to queryInfo() */
{
}
else
{
}
)
{
/* storage for hard disks of this format can neither be explicitly
* created by VirtualBox nor deleted, so we place the hard disk to
* Created state here and also add it to the registry */
m->state = MediumState_Created;
}
/* Confirm a successful initialization when it's the case */
return rc;
}
/**
* Initializes the medium object by opening the storage unit at the specified
* location. The enOpenMode parameter defines whether the medium will be opened
*
* This gets called by VirtualBox::OpenHardDisk(), OpenDVDImage and OpenFloppyImage();
* this also gets called by Machine::AttachDevice() and createImplicitDiffs()
* when new diff images are created.
*
* Note that the UUID, format and the parent of this medium will be
* determined when reading the medium storage unit, unless new values are
* specified by the parameters. If the detected or set parent is
* not known to VirtualBox, then this method will fail.
*
* @param aVirtualBox VirtualBox object.
* @param aLocation Storage unit location.
* @param aDeviceType Device type of medium.
* @param aSetImageId Whether to set the medium UUID or not.
* @param aImageId New medium UUID if @aSetId is true. Empty string means
* create a new UUID, and a zero UUID is invalid.
* @param aSetParentId Whether to set the parent UUID or not.
* @param aParentId New parent UUID if @aSetParentId is true. Empty string
* means create a new UUID, and a zero UUID is valid.
*/
{
/* Enclose the state transition NotReady->InInit->Ready */
AutoInitSpan autoInitSpan(this);
/* share VirtualBox weakly (parent remains NULL so far) */
/* there must be a storage unit */
m->state = MediumState_Created;
/* remember device type for correct unregistering later */
m->devType = aDeviceType;
/* cannot be a host drive */
m->hostDrive = false;
/* remember the open mode (defaults to ReadWrite) */
m->hddOpenMode = enOpenMode;
if (aDeviceType == DeviceType_HardDisk)
else
/* save the new uuid values, will be used by queryInfo() */
m->setImageId = !!aSetImageId;
m->setParentId = !!aSetParentId;
/* get all the information about the medium from the storage unit */
{
/* if the storage unit is not accessible, it's not acceptable for the
* newly opened media so convert this into an error */
if (m->state == MediumState_Inaccessible)
{
}
else
{
/* storage format must be detected by queryInfo() if the medium is accessible */
}
}
/* Confirm a successful initialization when it's the case */
return rc;
}
/**
* Initializes the medium object by loading its data from the given settings
*
* @param aVirtualBox VirtualBox object.
* @param aParent Parent medium disk or NULL for a root (base) medium.
* @param aDeviceType Device type of the medium.
* @param aNode Configuration settings.
*
* @note Locks VirtualBox for writing, the medium tree for writing.
*/
{
using namespace settings;
/* Enclose the state transition NotReady->InInit->Ready */
AutoInitSpan autoInitSpan(this);
/* share VirtualBox and parent weakly */
/* register with VirtualBox/parent early, since uninit() will
* unconditionally unregister on failure */
if (aParent)
{
// differencing medium: add to parent
}
/* see below why we don't call queryInfo() (and therefore treat the medium
* as inaccessible for now */
/* required */
/* assume not a host drive */
m->hostDrive = false;
/* optional */
/* required */
if (aDeviceType == DeviceType_HardDisk)
{
}
else
{
/// @todo handle host drive settings here as well?
else
}
/* optional, only for diffs, default is false; we can only auto-reset
* diff media so they must have a parent */
else
m->autoReset = false;
/* properties (after setting the format as it populates the map). Note that
* if some properties are not supported but preseint in the settings file,
* they will still be read and accessible (for possible backward
* compatibility; we can also clean them up from the XML upon next
* XML format version change if we wish) */
{
}
/* required */
if (aDeviceType == DeviceType_HardDisk)
{
/* type is only for base hard disks */
}
else
m->type = MediumType_Writethrough;
/* remember device type for correct unregistering later */
m->devType = aDeviceType;
LogFlowThisFunc(("m->strLocationFull='%s', m->strFormat=%s, m->id={%RTuuid}\n",
/* Don't call queryInfo() for registered media to prevent the calling
* thread (i.e. the VirtualBox server startup thread) from an unexpected
* freeze but mark it as initially inaccessible instead. The vital UUID,
* location and format properties are read from the registry file above; to
* get the actual state and the rest of the data, the user will have to call
* COMGETTER(State). */
/* load all children */
++it)
{
pHD.createObject();
this, // parent
med); // child data
}
/* Confirm a successful initialization when it's the case */
return rc;
}
/**
* Initializes the medium object by providing the host drive information.
*
* @param aVirtualBox VirtualBox object.
* @param aDeviceType Device type of the medium.
* @param aLocation Location of the host drive.
* @param aDescription Comment for this host drive.
*
* @note Locks VirtualBox lock for writing.
*/
{
/* Enclose the state transition NotReady->InInit->Ready */
AutoInitSpan autoInitSpan(this);
/* share VirtualBox weakly (parent remains NULL so far) */
/* fake up a UUID which is unique, but also reproducible */
RTUuidClear(&uuid);
if (aDeviceType == DeviceType_DVD)
else
/* use device name, adjusted to the end of uuid, shortened if necessary */
if (lenLocation > 12)
else
m->type = MediumType_Writethrough;
m->devType = aDeviceType;
m->state = MediumState_Created;
m->hostDrive = true;
m->strDescription = aDescription;
/// @todo generate uuid (similarly to host network interface uuid) from location and device type
return S_OK;
}
/**
* Uninitializes the instance.
*
* Called either from FinalRelease() or by the parent when it gets destroyed.
*
* @note All children of this medium get uninitialized by calling their
* uninit() methods.
*
* @note Caller must hold the tree lock of the medium tree this medium is on.
*/
{
/* Enclose the state transition Ready->InUninit->NotReady */
AutoUninitSpan autoUninitSpan(this);
if (autoUninitSpan.uninitDone())
return;
{
/* remove the caller reference we added in setFormat() */
m->formatObj->releaseCaller();
}
if (m->state == MediumState_Deleting)
{
/* we are being uninitialized after've been deleted by merge.
* Reparenting has already been done so don't touch it here (we are
* now orphans and removeDependentChild() will assert) */
}
else
{
++it)
{
}
if (m->pParent)
{
// this is a differencing disk: then remove it from the parent's children list
deparent();
}
}
}
/**
* Internal helper that removes "this" from the list of children of its
* parent. Used in uninit() and other places when reparenting is necessary.
*
* The caller must hold the medium tree lock!
*/
{
++it)
{
if (this == pParentsChild)
{
break;
}
}
}
/**
* Internal helper that removes "this" from the list of children of its
* parent. Used in uninit() and other places when reparenting is necessary.
*
* The caller must hold the medium tree lock!
*/
{
if (pParent)
}
////////////////////////////////////////////////////////////////////////////////
//
// IMedium public methods
//
////////////////////////////////////////////////////////////////////////////////
{
AutoCaller autoCaller(this);
return S_OK;
}
{
AutoCaller autoCaller(this);
return S_OK;
}
{
AutoCaller autoCaller(this);
// AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
/// @todo update m->description and save the global registry (and local
/// registries of portable VMs referring to this medium), this will also
/// require to add the mRegistered flag to data
}
{
AutoCaller autoCaller(this);
return S_OK;
}
{
AutoCaller autoCaller(this);
return S_OK;
}
{
AutoCaller autoCaller(this);
return S_OK;
}
{
AutoCaller autoCaller(this);
/// @todo NEWMEDIA for file names, add the default extension if no extension
/// is present (using the information from the VD backend which also implies
/// that one more parameter should be passed to setLocation() requesting
/// that functionality since it is only allwed when called from this method
/// @todo NEWMEDIA rename the file and set m->location on success, then save
/// the global registry (and local registries of portable VMs referring to
/// this medium), this will also require to add the mRegistered flag to data
}
{
AutoCaller autoCaller(this);
return S_OK;
}
{
AutoCaller autoCaller(this);
*aDeviceType = m->devType;
return S_OK;
}
{
AutoCaller autoCaller(this);
*aHostDrive = m->hostDrive;
return S_OK;
}
{
AutoCaller autoCaller(this);
return S_OK;
}
{
AutoCaller autoCaller(this);
/* no need to lock, m->strFormat is const */
return S_OK;
}
{
AutoCaller autoCaller(this);
/* no need to lock, m->formatObj is const */
return S_OK;
}
{
AutoCaller autoCaller(this);
return S_OK;
}
{
AutoCaller autoCaller(this);
// we access mParent and members
AutoMultiWriteLock2 mlock(&m->pVirtualBox->getMediaTreeLockHandle(), this->lockHandle() COMMA_LOCKVAL_SRC_POS);
switch (m->state)
{
case MediumState_Created:
case MediumState_Inaccessible:
break;
default:
return setStateError();
}
{
/* Nothing to do */
return S_OK;
}
/* cannot change the type of a differencing medium */
if (m->pParent)
return setError(VBOX_E_INVALID_OBJECT_STATE,
tr("Cannot change the type of medium '%s' because it is a differencing medium"),
m->strLocationFull.raw());
/* cannot change the type of a medium being in use by more than one VM */
return setError(VBOX_E_INVALID_OBJECT_STATE,
tr("Cannot change the type of medium '%s' because it is attached to %d virtual machines"),
switch (aType)
{
case MediumType_Normal:
case MediumType_Immutable:
{
/* normal can be easily converted to immutable and vice versa even
* if they have children as long as they are not attached to any
* machine themselves */
break;
}
case MediumType_Writethrough:
case MediumType_Shareable:
{
/* cannot change to writethrough or shareable if there are children */
if (getChildren().size() != 0)
return setError(VBOX_E_OBJECT_IN_USE,
tr("Cannot change type for medium '%s' since it has %d child media"),
if (aType == MediumType_Shareable)
{
if (!(variant & MediumVariant_Fixed))
return setError(VBOX_E_INVALID_OBJECT_STATE,
tr("Cannot change type for medium '%s' to 'Shareable' since it is a dynamic medium storage unit"),
m->strLocationFull.raw());
}
break;
}
default:
}
// save the global settings; for that we should hold only the VirtualBox lock
return rc;
}
{
AutoCaller autoCaller(this);
/* we access mParent */
return S_OK;
}
{
AutoCaller autoCaller(this);
/* we access children */
return S_OK;
}
{
return S_OK;
}
{
AutoCaller autoCaller(this);
/* isRadOnly() will do locking */
*aReadOnly = isReadOnly();
return S_OK;
}
{
{
AutoCaller autoCaller(this);
/* we access mParent */
{
*aLogicalSize = m->logicalSize;
return S_OK;
}
}
/* We assume that some backend may decide to return a meaningless value in
* response to VDGetSize() for differencing media and therefore always
* ask the base medium ourselves. */
}
{
AutoCaller autoCaller(this);
*aAutoReset = FALSE;
else
*aAutoReset = m->autoReset;
return S_OK;
}
{
AutoCaller autoCaller(this);
return setError(VBOX_E_NOT_SUPPORTED,
tr("Medium '%s' is not differencing"),
m->strLocationFull.raw());
if (m->autoReset != !!aAutoReset)
{
m->autoReset = !!aAutoReset;
// save the global settings; for that we should hold only the VirtualBox lock
return m->pVirtualBox->saveSettings();
}
return S_OK;
}
{
AutoCaller autoCaller(this);
return S_OK;
}
{
AutoCaller autoCaller(this);
{
size_t i = 0;
{
}
}
return S_OK;
}
{
AutoCaller autoCaller(this);
/* queryInfo() locks this for writing. */
switch (m->state)
{
case MediumState_Created:
case MediumState_Inaccessible:
case MediumState_LockedRead:
{
break;
}
default:
break;
}
return rc;
}
{
AutoCaller autoCaller(this);
{
{
/* if the medium is attached to the machine in the current state, we
* return its ID as the first element of the array */
if (it->fInCurState)
++size;
if (size > 0)
{
size_t j = 0;
if (it->fInCurState)
++jt, ++j)
{
}
}
break;
}
}
return S_OK;
}
/**
* @note @a aState may be NULL if the state value is not needed (only for
* in-process calls).
*/
{
AutoCaller autoCaller(this);
/* Wait for a concurrently running queryInfo() to complete */
while (m->queryInfoRunning)
{
}
/* return the current state before */
if (aState)
switch (m->state)
{
case MediumState_Created:
case MediumState_Inaccessible:
case MediumState_LockedRead:
{
++m->readers;
/* Remember pre-lock state */
if (m->state != MediumState_LockedRead)
m->preLockState = m->state;
m->state = MediumState_LockedRead;
break;
}
default:
{
rc = setStateError();
break;
}
}
return rc;
}
/**
* @note @a aState may be NULL if the state value is not needed (only for
* in-process calls).
*/
{
AutoCaller autoCaller(this);
switch (m->state)
{
case MediumState_LockedRead:
{
--m->readers;
/* Reset the state after the last reader */
if (m->readers == 0)
{
m->state = m->preLockState;
/* There are cases where we inject the deleting state into
* a medium locked for reading. Make sure #unmarkForDeletion()
* gets the right state afterwards. */
if (m->preLockState == MediumState_Deleting)
}
break;
}
default:
{
tr("Medium '%s' is not locked for reading"),
m->strLocationFull.raw());
break;
}
}
/* return the current state after */
if (aState)
return rc;
}
/**
* @note @a aState may be NULL if the state value is not needed (only for
* in-process calls).
*/
{
AutoCaller autoCaller(this);
/* Wait for a concurrently running queryInfo() to complete */
while (m->queryInfoRunning)
{
}
/* return the current state before */
if (aState)
switch (m->state)
{
case MediumState_Created:
case MediumState_Inaccessible:
{
m->preLockState = m->state;
m->state = MediumState_LockedWrite;
break;
}
default:
{
rc = setStateError();
break;
}
}
return rc;
}
/**
* @note @a aState may be NULL if the state value is not needed (only for
* in-process calls).
*/
{
AutoCaller autoCaller(this);
switch (m->state)
{
case MediumState_LockedWrite:
{
m->state = m->preLockState;
/* There are cases where we inject the deleting state into
* a medium locked for writing. Make sure #unmarkForDeletion()
* gets the right state afterwards. */
if (m->preLockState == MediumState_Deleting)
break;
}
default:
{
tr("Medium '%s' is not locked for writing"),
m->strLocationFull.raw());
break;
}
}
/* return the current state after */
if (aState)
return rc;
}
{
AutoCaller autoCaller(this);
// make a copy of VirtualBox pointer which gets nulled by uninit()
bool fNeedsSaveSettings = false;
if (fNeedsSaveSettings)
{
}
return rc;
}
{
AutoCaller autoCaller(this);
return setError(VBOX_E_OBJECT_NOT_FOUND,
return S_OK;
}
{
AutoCaller autoCaller(this);
switch (m->state)
{
case MediumState_Created:
case MediumState_Inaccessible:
break;
default:
return setStateError();
}
return setError(VBOX_E_OBJECT_NOT_FOUND,
tr("Property '%ls' does not exist"),
aName);
else
// save the global settings; for that we should hold only the VirtualBox lock
return rc;
}
{
AutoCaller autoCaller(this);
/// @todo make use of aNames according to the documentation
size_t i = 0;
++it)
{
++i;
}
return S_OK;
}
{
AutoCaller autoCaller(this);
/* first pass: validate names */
for (size_t i = 0;
++i)
{
return setError(VBOX_E_OBJECT_NOT_FOUND,
}
/* second pass: assign */
for (size_t i = 0;
++i)
{
else
}
// saveSettings needs vbox lock
return rc;
}
{
AutoCaller autoCaller(this);
try
{
if ( !(aVariant & MediumVariant_Fixed)
throw setError(VBOX_E_NOT_SUPPORTED,
tr("Medium format '%s' does not support dynamic storage creation"),
if ( (aVariant & MediumVariant_Fixed)
throw setError(VBOX_E_NOT_SUPPORTED,
tr("Medium format '%s' does not support fixed storage creation"),
if (m->state != MediumState_NotCreated)
throw setStateError();
static_cast<IMedium*>(this),
TRUE /* aCancelable */);
throw rc;
/* setup task object to carry out the operation asynchronously */
aVariant);
throw rc;
m->state = MediumState_Creating;
}
{
}
delete pTask;
return rc;
}
{
AutoCaller autoCaller(this);
bool fNeedsSaveSettings = false;
false /* aWait */,
if (fNeedsSaveSettings)
{
m->pVirtualBox->saveSettings();
}
return rc;
}
{
AutoCaller autoCaller(this);
if (m->type == MediumType_Writethrough)
return setError(VBOX_E_INVALID_OBJECT_STATE,
tr("Medium type of '%s' is Writethrough"),
m->strLocationFull.raw());
else if (m->type == MediumType_Shareable)
return setError(VBOX_E_INVALID_OBJECT_STATE,
tr("Medium type of '%s' is Shareable"),
m->strLocationFull.raw());
/* Apply the normal locking logic to the entire chain. */
true /* fMediumLockWrite */,
this,
{
delete pMediumLockList;
return rc;
}
delete pMediumLockList;
else
return rc;
}
{
AutoCaller autoCaller(this);
bool fMergeForward = false;
NULL /* pfNeedsSaveSettings */);
else
return rc;
}
{
AutoCaller autoCaller(this);
if (aParent)
try
{
// locking: we need the tree lock first because we access parent pointers
// and we need to write-lock the media involved
throw pTarget->setStateError();
/* Build the source lock list. */
false /* fMediumLockWrite */,
NULL,
{
delete pSourceMediumLockList;
throw rc;
}
/* Build the target lock list (including the to-be parent chain). */
true /* fMediumLockWrite */,
{
delete pSourceMediumLockList;
delete pTargetMediumLockList;
throw rc;
}
{
delete pSourceMediumLockList;
delete pTargetMediumLockList;
tr("Failed to lock source media '%s'"),
getLocationFull().raw());
}
{
delete pSourceMediumLockList;
delete pTargetMediumLockList;
tr("Failed to lock target media '%s'"),
}
static_cast <IMedium *>(this),
TRUE /* aCancelable */);
{
delete pSourceMediumLockList;
delete pTargetMediumLockList;
throw rc;
}
/* setup task object to carry out the operation asynchronously */
throw rc;
}
{
}
delete pTask;
return rc;
}
{
AutoCaller autoCaller(this);
try
{
/* We need to lock both the current object, and the tree lock (would
* cause a lock order violation otherwise) for createMediumLockList. */
this->lockHandle()
/* Build the medium lock list. */
true /* fMediumLockWrite */,
NULL,
{
delete pMediumLockList;
throw rc;
}
{
delete pMediumLockList;
tr("Failed to lock media when compacting '%s'"),
getLocationFull().raw());
}
static_cast <IMedium *>(this),
TRUE /* aCancelable */);
{
delete pMediumLockList;
throw rc;
}
/* setup task object to carry out the operation asynchronously */
throw rc;
}
{
}
delete pTask;
return rc;
}
{
AutoCaller autoCaller(this);
}
{
AutoCaller autoCaller(this);
try
{
/* canClose() needs the tree lock */
this->lockHandle()
throw setError(VBOX_E_NOT_SUPPORTED,
tr("Medium type of '%s' is not differencing"),
m->strLocationFull.raw());
throw rc;
/* Build the medium lock list. */
true /* fMediumLockWrite */,
NULL,
{
delete pMediumLockList;
throw rc;
}
{
delete pMediumLockList;
tr("Failed to lock media when resetting '%s'"),
getLocationFull().raw());
}
static_cast<IMedium*>(this),
FALSE /* aCancelable */);
throw rc;
/* setup task object to carry out the operation asynchronously */
throw rc;
}
{
}
else
{
/* Note: on success, the task will unlock this */
{
}
delete pTask;
}
return rc;
}
////////////////////////////////////////////////////////////////////////////////
//
// Medium public internal methods
//
////////////////////////////////////////////////////////////////////////////////
/**
* Internal method to return the medium's parent medium. Must have caller + locking!
* @return
*/
{
return m->pParent;
}
/**
* Internal method to return the medium's list of child media. Must have caller + locking!
* @return
*/
{
return m->llChildren;
}
/**
* Internal method to return the medium's GUID. Must have caller + locking!
* @return
*/
{
return m->id;
}
/**
* Internal method to return the medium's state. Must have caller + locking!
* @return
*/
{
return m->state;
}
/**
* Internal method to return the medium's variant. Must have caller + locking!
* @return
*/
{
return m->variant;
}
/**
* Internal method which returns true if this medium represents a host drive.
* @return
*/
bool Medium::isHostDrive() const
{
return m->hostDrive;
}
/**
* Internal method to return the medium's location. Must have caller + locking!
* @return
*/
{
return m->strLocation;
}
/**
* Internal method to return the medium's full location. Must have caller + locking!
* @return
*/
{
return m->strLocationFull;
}
/**
* Internal method to return the medium's format string. Must have caller + locking!
* @return
*/
{
return m->strFormat;
}
/**
* Internal method to return the medium's format object. Must have caller + locking!
* @return
*/
{
return m->formatObj;
}
/**
* Internal method to return the medium's size. Must have caller + locking!
* @return
*/
{
return m->size;
}
/**
* Adds the given machine and optionally the snapshot to the list of the objects
* this medium is attached to.
*
* @param aMachineId Machine ID.
* @param aSnapshotId Snapshot ID; when non-empty, adds a snapshot attachment.
*/
{
LogFlowThisFunc(("ENTER, aMachineId: {%RTuuid}, aSnapshotId: {%RTuuid}\n", aMachineId.raw(), aSnapshotId.raw()));
AutoCaller autoCaller(this);
switch (m->state)
{
case MediumState_Created:
case MediumState_Inaccessible:
case MediumState_LockedRead:
case MediumState_LockedWrite:
break;
default:
return setStateError();
}
if (m->numCreateDiffTasks > 0)
return setError(VBOX_E_OBJECT_IN_USE,
tr("Cannot attach medium '%s' {%RTuuid}: %u differencing child media are being created"),
m->strLocationFull.raw(),
m->numCreateDiffTasks);
{
return S_OK;
}
// if the caller has not supplied a snapshot ID, then we're attaching
// to a machine a medium which represents the machine's current state,
// so set the flag
if (aSnapshotId.isEmpty())
{
/* sanity: no duplicate attachments */
it->fInCurState = true;
return S_OK;
}
// otherwise: a snapshot medium is being attached
/* sanity: no duplicate attachments */
++jt)
{
if (idOldSnapshot == aSnapshotId)
{
#ifdef DEBUG
dumpBackRefs();
#endif
return setError(VBOX_E_OBJECT_IN_USE,
tr("Cannot attach medium '%s' {%RTuuid} from snapshot '%RTuuid': medium is already in use by this snapshot!"),
m->strLocationFull.raw(),
aSnapshotId.raw(),
idOldSnapshot.raw());
}
}
it->fInCurState = false;
return S_OK;
}
/**
* Removes the given machine and optionally the snapshot from the list of the
* objects this medium is attached to.
*
* @param aMachineId Machine ID.
* @param aSnapshotId Snapshot ID; when non-empty, removes the snapshot
* attachment.
*/
{
AutoCaller autoCaller(this);
if (aSnapshotId.isEmpty())
{
/* remove the current state attachment */
it->fInCurState = false;
}
else
{
/* remove the snapshot attachment */
}
/* if the backref becomes empty, remove it */
return S_OK;
}
/**
* Internal method to return the medium's list of backrefs. Must have caller + locking!
* @return
*/
{
return NULL;
}
{
return NULL;
return NULL;
}
#ifdef DEBUG
/**
* Debugging helper that gets called after VirtualBox initialization that writes all
* machine backreferences to the debug log.
*/
void Medium::dumpBackRefs()
{
AutoCaller autoCaller(this);
++it2)
{
LogFlowThisFunc((" Backref from machine {%RTuuid} (fInCurState: %d)\n", ref.machineId.raw(), ref.fInCurState));
++jt2)
{
}
}
}
#endif
/**
* Checks if the given change of \a aOldPath to \a aNewPath affects the location
* of this media and updates it if necessary to reflect the new location.
*
* @param aOldPath Old path (full).
* @param aNewPath New path (full).
*
* @note Locks this object for writing.
*/
{
AutoCaller autoCaller(this);
{
}
return S_OK;
}
/**
* Returns the base medium of the media chain this medium is part of.
*
* The base medium is found by walking up the parent-child relationship axis.
* If the medium doesn't have a parent (i.e. it's a base medium), it
* returns itself in response to this method.
*
* @param aLevel Where to store the number of ancestors of this medium
* (zero for the base), may be @c NULL.
*
* @note Locks medium tree for reading.
*/
{
AutoCaller autoCaller(this);
/* we access mParent */
pBase = this;
level = 0;
if (m->pParent)
{
for (;;)
{
break;
++level;
}
}
return pBase;
}
/**
* Returns @c true if this medium cannot be modified because it has
* dependants (children) or is part of the snapshot. Related to the medium
* type and posterity, not to the current media state.
*
* @note Locks this object and medium tree for reading.
*/
bool Medium::isReadOnly()
{
AutoCaller autoCaller(this);
/* we access children */
switch (m->type)
{
case MediumType_Normal:
{
if (getChildren().size() != 0)
return true;
return true;
return false;
}
case MediumType_Immutable:
return true;
case MediumType_Writethrough:
case MediumType_Shareable:
return false;
default:
break;
}
AssertFailedReturn(false);
}
/**
* Saves medium data by appending a new child node to the given
* parent XML settings node.
*
* @param data Settings struct to be updated.
*
* @note Locks this object, medium tree and children for reading.
*/
{
AutoCaller autoCaller(this);
/* we access mParent */
/* optional, only for diffs, default is false */
if (m->pParent)
else
data.fAutoReset = false;
/* optional */
/* optional properties */
++it)
{
/* only save properties that have non-default values */
{
}
}
/* only for base media */
/* save all children */
++it)
{
}
return S_OK;
}
/**
* Compares the location of this medium to the given location.
*
* The comparison takes the location details into account. For example, if the
* location is a file in the host's filesystem, a case insensitive comparison
* will be performed for case insensitive filesystems.
*
* @param aLocation Location to compare to (as is).
* @param aResult Where to store the result of comparison: 0 if locations
* are equal, 1 if this object's location is greater than
* the specified location, and -1 otherwise.
*/
{
AutoCaller autoCaller(this);
/// @todo NEWMEDIA delegate the comparison to the backend?
{
/* For locations represented by files, append the default path if
* only the name is given, and then get the full path. */
if (!RTPathHavePath(aLocation))
{
}
if (RT_FAILURE(vrc))
return setError(VBOX_E_FILE_ERROR,
tr("Invalid medium storage file location '%s' (%Rrc)"),
vrc);
}
else
return S_OK;
}
/**
* Constructs a medium lock list for this medium. The lock is not taken.
*
* @note Locks the medium tree for reading.
*
* @param fFailIfInaccessible If true, this fails with an error if a medium is inaccessible. If false,
* inaccessible media are silently skipped and not locked (i.e. their state remains "Inaccessible");
* this is necessary for a VM's removable media VM startup for which we do not want to fail.
* @param fMediumLockWrite Whether to associate a write lock with this medium.
* @param pToBeParent Medium which will become the parent of this medium.
* @param mediumLockList Where to store the resulting list.
*/
bool fMediumLockWrite,
{
AutoCaller autoCaller(this);
/* we access parent medium objects */
/* paranoid sanity checking if the medium has a to-be parent medium */
if (pToBeParent)
{
}
{
// need write lock for RefreshState if medium is inaccessible
/* Accessibility check must be first, otherwise locking interferes
* with getting the medium state. Lock lists are not created for
* fun, and thus getting the medium status is no luxury. */
if (mediumState == MediumState_Inaccessible)
{
if (mediumState == MediumState_Inaccessible)
{
// ignore inaccessible ISO media and silently return S_OK,
// otherwise VM startup (esp. restore) may fail without good reason
if (!fFailIfInaccessible)
return S_OK;
// otherwise report an error
/* collect multiple errors */
"%ls",
// error message will be something like
// "Could not open the medium ... VD: error VERR_FILE_NOT_FOUND opening image file ... (VERR_FILE_NOT_FOUND).
}
}
if (pMedium == this)
else
{
pToBeParent = NULL;
}
}
return mrc;
}
/**
* Returns a preferred format for differencing media.
*/
{
AutoCaller autoCaller(this);
/* check that our own format supports diffs */
/* use the default format if not */
return m->pVirtualBox->getDefaultHardDiskFormat();
/* m->strFormat is const, no need to lock */
return m->strFormat;
}
/**
* Returns the medium device type. Must have caller + locking!
* @return
*/
{
return m->devType;
}
/**
* Returns the medium type. Must have caller + locking!
* @return
*/
{
return m->type;
}
/**
* Returns a short version of the location attribute.
*
* @note Must be called from under this object's read or write lock.
*/
{
return name;
}
/**
* Sets the value of m->strLocation and calculates the value of m->strLocationFull.
*
* Treats non-FS-path locations specially, and prepends the default medium
* folder if the given location string does not contain any path information
* at all.
*
* Also, if the specified location is a file path that ends with '/' then the
* file name part will be generated by this method automatically in the format
* '{<uuid>}.<ext>' where <uuid> is a fresh UUID that this method will generate
* and assign to this medium, and <ext> is the default extension for this
* medium's storage format. Note that this procedure requires the media state to
* be NotCreated and will return a failure otherwise.
*
* @param aLocation Location of the storage unit. If the location is a FS-path,
* then it can be relative to the VirtualBox home directory.
* @param aFormat Optional fallback format if it is an import and the format
* cannot be determined.
*
* @note Must be called from under this object's write lock.
*/
{
AutoCaller autoCaller(this);
/* formatObj may be null only when initializing from an existing path and
* no format is known yet */
&& m->state != MediumState_NotCreated
E_FAIL);
/* are we dealing with a new medium constructed using the existing
* location? */
if ( isImport
&& !m->hostDrive))
{
if (m->state == MediumState_NotCreated)
{
/* must be a file (formatObj must be already known) */
{
/* no file name is given (either an empty string or ends with a
* slash), generate a new UUID + file name if the state allows
* this */
("Must be at least one extension if it is MediumFormatCapabilities_File\n"),
E_FAIL);
("Default extension must not be empty\n"),
E_FAIL);
}
}
/* append the default folder if no path is given */
/* get the full file name */
if (RT_FAILURE(vrc))
return setError(VBOX_E_FILE_ERROR,
tr("Invalid medium storage file location '%s' (%Rrc)"),
/* detect the backend from the storage unit if importing */
if (isImport)
{
char *backendName = NULL;
/* is it a file? */
{
if (RT_SUCCESS(vrc))
}
if (RT_SUCCESS(vrc))
{
}
{
/* assume it's not a file, restore the original location */
}
if (RT_FAILURE(vrc))
{
return setError(VBOX_E_FILE_ERROR,
tr("Could not find file for the medium '%s' (%Rrc)"),
return setError(VBOX_E_IPRT_ERROR,
tr("Could not get the storage format of the medium '%s' (%Rrc)"),
else
{
/* setFormat() must not fail since we've just used the backend so
* the format object must be there */
}
}
else
{
/* setFormat() must not fail since we've just used the backend so
* the format object must be there */
}
}
/* is it still a file? */
{
m->strLocation = location;
m->strLocationFull = locationFull;
if (m->state == MediumState_NotCreated)
{
/* assign a new UUID (this UUID will be used when calling
* VDCreateBase/VDCreateDiff as a wanted UUID). Note that we
* also do that if we didn't generate it to make sure it is
* either generated by us or reset to null */
}
}
else
{
m->strLocation = locationFull;
m->strLocationFull = locationFull;
}
}
else
{
m->strLocation = aLocation;
m->strLocationFull = aLocation;
}
return S_OK;
}
/**
* Queries information from the medium.
*
* As a result of this call, the accessibility state and data members such as
* size and description will be updated with the current information.
*
* @note This method may block during a system I/O call that checks storage
* accessibility.
*
* @note Locks medium tree for reading and writing (for new diff media checked
* for the first time). Locks mParent for reading. Locks this object for
* writing.
*/
{
if ( m->state != MediumState_Created
&& m->state != MediumState_Inaccessible
&& m->state != MediumState_LockedRead)
return E_FAIL;
int vrc = VINF_SUCCESS;
/* check if a blocking queryInfo() call is in progress on some other thread,
* and wait for it to finish if so instead of querying data ourselves */
if (m->queryInfoRunning)
{
|| m->state == MediumState_LockedWrite);
return S_OK;
}
bool success = false;
/* are we dealing with a new medium constructed using the existing
* location? */
unsigned uOpenFlags = VD_OPEN_FLAGS_INFO;
/* Note that we don't use VD_OPEN_FLAGS_READONLY when opening new
* media because that would prevent necessary modifications
* when opening media of some third-party formats for the first
* time in VirtualBox (such as VMDK for which VDOpen() needs to
* generate an UUID if it is missing) */
if ( (m->hddOpenMode == OpenReadOnly)
|| !isImport
)
/* Open shareable medium with the appropriate flags */
if (m->type == MediumType_Shareable)
/* Lock the medium, which makes the behavior much more consistent */
else
/* Copies of the input state fields which are not read-only,
* as we're dropping the lock. CAUTION: be extremely careful what
* you do with the contents of this medium object, as you will
* create races if there are concurrent changes. */
/* "Output" values which can't be set because the lock isn't held
* at the time the values are determined. */
uint64_t mediumSize = 0;
/* leave the lock before a lengthy operation */
m->queryInfoRunning = true;
try
{
/* skip accessibility checks for host drives */
if (m->hostDrive)
{
success = true;
throw S_OK;
}
try
{
/** @todo This kind of opening of media is assuming that diff
* media can be opened as base media. Should be documented that
* it must work for all medium format backends. */
m->vdDiskIfaces);
if (RT_FAILURE(vrc))
{
throw S_OK;
}
{
/* Modify the UUIDs if necessary. The associated fields are
* not modified by other code, so no need to copy. */
if (m->setImageId)
{
}
if (m->setParentId)
{
}
/* zap the information, these are no long-term members */
m->setImageId = false;
m->setParentId = false;
/* check the UUID */
if (isImport)
{
// only when importing a VDMK that has no UUID, create one in memory
}
else
{
{
tr("UUID {%RTuuid} of the medium '%s' does not match the value {%RTuuid} stored in the media registry ('%s')"),
&uuid,
throw S_OK;
}
}
}
else
{
/* the backend does not support storing UUIDs within the
* underlying storage so use what we store in XML */
/* generate an UUID for an imported UUID-less medium */
if (isImport)
{
if (m->setImageId)
else
}
}
/* get the medium variant */
unsigned uImageFlags;
if (uImageFlags & VD_IMAGE_FLAGS_DIFF)
{
if (isImport)
{
/* the parent must be known to us. Note that we freely
* call locking methods of mVirtualBox and parent, as all
* relevant locks must be already held. There may be no
* concurrent access to the just opened medium on other
* threads yet (and init() will fail if this method reports
* MediumState_Inaccessible) */
{
tr("Parent medium with UUID {%RTuuid} of the medium '%s' is not found in the media registry ('%s')"),
throw S_OK;
}
/* we set mParent & children() */
}
else
{
/* we access mParent */
/* check that parent UUIDs match. Note that there's no need
* for the parent's AutoCaller (our lifetime is bound to
* it) */
{
tr("Medium type of '%s' is differencing but it is not associated with any parent medium in the media registry ('%s')"),
throw S_OK;
}
{
tr("Parent UUID {%RTuuid} of the medium '%s' does not match UUID {%RTuuid} of its parent medium stored in the media registry ('%s')"),
throw S_OK;
}
/// @todo NEWMEDIA what to do if the parent is not
/// accessible while the diff is? Probably nothing. The
/// real code will detect the mismatch anyway.
}
}
success = true;
}
{
}
}
{
}
if (isImport)
if (success)
{
m->size = mediumSize;
m->logicalSize = mediumLogicalSize;
m->strLastAccessError.setNull();
}
else
{
LogWarningFunc(("'%s' is not accessible (error='%s', rc=%Rhrc, vrc=%Rrc)\n",
}
/* inform other callers if there are any */
m->queryInfoRunning = false;
/* Set the proper state according to the result of the check */
if (success)
else
else
return rc;
}
/**
* Sets the extended error info according to the current media state.
*
* @note Must be called from under this object's write or read lock.
*/
{
switch (m->state)
{
case MediumState_NotCreated:
{
tr("Storage for the medium '%s' is not created"),
m->strLocationFull.raw());
break;
}
case MediumState_Created:
{
tr("Storage for the medium '%s' is already created"),
m->strLocationFull.raw());
break;
}
case MediumState_LockedRead:
{
tr("Medium '%s' is locked for reading by another task"),
m->strLocationFull.raw());
break;
}
case MediumState_LockedWrite:
{
tr("Medium '%s' is locked for writing by another task"),
m->strLocationFull.raw());
break;
}
case MediumState_Inaccessible:
{
/* be in sync with Console::powerUpThread() */
if (!m->strLastAccessError.isEmpty())
tr("Medium '%s' is not accessible. %s"),
else
tr("Medium '%s' is not accessible"),
m->strLocationFull.raw());
break;
}
case MediumState_Creating:
{
tr("Storage for the medium '%s' is being created"),
m->strLocationFull.raw());
break;
}
case MediumState_Deleting:
{
tr("Storage for the medium '%s' is being deleted"),
m->strLocationFull.raw());
break;
}
default:
{
AssertFailed();
break;
}
}
return rc;
}
/**
* Implementation for the public Medium::Close() with the exception of calling
* VirtualBox::saveSettings(), in case someone wants to call this for several
* media.
*
* After this returns with success, uninit() has been called on the medium, and
* the object is no longer usable ("not ready" state).
*
* @param pfNeedsSaveSettings Optional pointer to a bool that must have been initialized to false and that will be set to true
* by this function if the caller should invoke VirtualBox::saveSettings() because the global settings have changed.
* This only works in "wait" mode; otherwise saveSettings gets called automatically by the thread that was created,
* and this parameter is ignored.
* @param autoCaller AutoCaller instance which must have been created on the caller's stack for this medium. This gets released here
* upon which the Medium instance gets uninitialized.
* @return
*/
{
this->lockHandle()
bool wasCreated = true;
switch (m->state)
{
case MediumState_NotCreated:
wasCreated = false;
break;
case MediumState_Created:
case MediumState_Inaccessible:
break;
default:
return setStateError();
}
return setError(VBOX_E_OBJECT_IN_USE,
tr("Medium '%s' cannot be closed because it is still attached to %d virtual machines"),
// perform extra media-dependent close checks
if (wasCreated)
{
// remove from the list of known media before performing actual
// uninitialization (to keep the media registry consistent on
// failure to do so)
}
// leave the AutoCaller, as otherwise uninit() will simply hang
// Keep the locks held until after uninit, as otherwise the consistency
// of the medium tree cannot be guaranteed.
uninit();
return rc;
}
/**
* Deletes the medium storage unit.
*
* If @a aProgress is not NULL but the object it points to is @c null then a new
* progress object will be created and assigned to @a *aProgress on success,
* otherwise the existing progress object is used. If Progress is NULL, then no
*
* When @a aWait is @c false, this method will create a thread to perform the
* delete operation asynchronously and will return immediately. Otherwise, it
* will perform the operation on the calling thread and will not return to the
* caller until the operation is completed. Note that @a aProgress cannot be
* NULL when @a aWait is @c false (this method will assert in this case).
*
* completion.
* @param aWait @c true if this method should block instead of creating
* an asynchronous thread.
* @param pfNeedsSaveSettings Optional pointer to a bool that must have been initialized to false and that will be set to true
* by this function if the caller should invoke VirtualBox::saveSettings() because the global settings have changed.
* This only works in "wait" mode; otherwise saveSettings gets called automatically by the thread that was created,
* and this parameter is ignored.
*
* @note Locks mVirtualBox and this object for writing. Locks medium tree for
* writing.
*/
bool aWait,
bool *pfNeedsSaveSettings)
{
AutoCaller autoCaller(this);
try
{
/* we're accessing the media tree, and canClose() needs it too */
this->lockHandle()
throw setError(VBOX_E_NOT_SUPPORTED,
tr("Medium format '%s' does not support storage deletion"),
/* Note that we are fine with Inaccessible state too: a) for symmetry
* with create calls and b) because it doesn't really harm to try, if
* it is really inaccessible, the delete operation will fail anyway.
* Accepting Inaccessible state is especially important because all
* registered media are initially Inaccessible upon VBoxSVC startup
* until COMGETTER(RefreshState) is called. Accept Deleting state
* because some callers need to put the medium in this state early
* to prevent races. */
switch (m->state)
{
case MediumState_Created:
case MediumState_Deleting:
case MediumState_Inaccessible:
break;
default:
throw setStateError();
}
{
++it)
{
if (strMachines.length())
}
#ifdef DEBUG
dumpBackRefs();
#endif
throw setError(VBOX_E_OBJECT_IN_USE,
tr("Cannot delete storage: medium '%s' is still attached to the following %d virtual machine(s): %s"),
m->strLocationFull.c_str(),
strMachines.c_str());
}
throw rc;
/* go to Deleting state, so that the medium is not actually locked */
if (m->state != MediumState_Deleting)
{
rc = markForDeletion();
throw rc;
}
/* Build the medium lock list. */
true /* fMediumLockWrite */,
NULL,
{
delete pMediumLockList;
throw rc;
}
{
delete pMediumLockList;
tr("Failed to lock media when deleting '%s'"),
getLocationFull().raw());
}
/* try to remove from the list of known media before performing
* actual deletion (we favor the consistency of the media registry
* which would have been broken if unregisterWithVirtualBox() failed
* after we successfully deleted the storage) */
throw rc;
// no longer need lock
{
/* use the existing progress object... */
/* ...but create a new one if it is null */
{
static_cast<IMedium*>(this),
FALSE /* aCancelable */);
throw rc;
}
}
throw rc;
}
{
if (aWait)
else
}
else
{
if (pTask)
delete pTask;
/* Undo deleting state if necessary. */
}
return rc;
}
/**
* Mark a medium for deletion.
*
* @note Caller must hold the write lock on this medium!
*/
{
switch (m->state)
{
case MediumState_Created:
case MediumState_Inaccessible:
m->preLockState = m->state;
m->state = MediumState_Deleting;
return S_OK;
default:
return setStateError();
}
}
/**
* Removes the "mark for deletion".
*
* @note Caller must hold the write lock on this medium!
*/
{
switch (m->state)
{
case MediumState_Deleting:
m->state = m->preLockState;
return S_OK;
default:
return setStateError();
}
}
/**
* Mark a medium for deletion which is in locked state.
*
* @note Caller must hold the write lock on this medium!
*/
{
if ( ( m->state == MediumState_LockedRead
|| m->state == MediumState_LockedWrite)
&& m->preLockState == MediumState_Created)
{
return S_OK;
}
else
return setStateError();
}
/**
* Removes the "mark for deletion" for a medium in locked state.
*
* @note Caller must hold the write lock on this medium!
*/
{
if ( ( m->state == MediumState_LockedRead
|| m->state == MediumState_LockedWrite)
&& m->preLockState == MediumState_Deleting)
{
return S_OK;
}
else
return setStateError();
}
/**
* Creates a new differencing storage unit using the format of the given target
* medium and the location. Note that @c aTarget must be NotCreated.
*
* The @a aMediumLockList parameter contains the associated medium lock list,
* which must be in locked state. If @a aWait is @c true then the caller is
* responsible for unlocking.
*
* If @a aProgress is not NULL but the object it points to is @c null then a
* new progress object will be created and assigned to @a *aProgress on
* success, otherwise the existing progress object is used. If @a aProgress is
*
* When @a aWait is @c false, this method will create a thread to perform the
* create operation asynchronously and will return immediately. Otherwise, it
* will perform the operation on the calling thread and will not return to the
* caller until the operation is completed. Note that @a aProgress cannot be
* NULL when @a aWait is @c false (this method will assert in this case).
*
* @param aTarget Target medium.
* @param aVariant Precise medium variant to create.
* @param aMediumLockList List of media which should be locked.
* operation completion.
* @param aWait @c true if this method should block instead of
* creating an asynchronous thread.
* @param pfNeedsSaveSettings Optional pointer to a bool that must have been
* initialized to false and that will be set to true
* by this function if the caller should invoke
* VirtualBox::saveSettings() because the global
* settings have changed. This only works in "wait"
* mode; otherwise saveSettings is called
* automatically by the thread that was created,
* and this parameter is ignored.
*
* @note Locks this object and @a aTarget for writing.
*/
bool aWait,
bool *pfNeedsSaveSettings)
{
AutoCaller autoCaller(this);
try
{
throw aTarget->setStateError();
/* Check that the medium is not attached to the current state of
* any VM referring to it. */
++it)
{
if (it->fInCurState)
{
/* Note: when a VM snapshot is being taken, all normal media
* attached to the VM in the current state will be, as an
* exception, also associated with the snapshot which is about
* to create (see SnapshotMachine::init()) before deassociating
* them from the current state (which takes place only on
* success in Machine::fixupHardDisks()), so that the size of
* snapshotIds will be 1 in this case. The extra condition is
* used to filter out this legal situation. */
tr("Medium '%s' is attached to a virtual machine with UUID {%RTuuid}. No differencing media based on it may be created until it is detached"),
}
}
{
/* use the existing progress object... */
/* ...but create a new one if it is null */
{
static_cast<IMedium*>(this),
TRUE /* aCancelable */);
throw rc;
}
}
aWait /* fKeepMediumLockList */);
throw rc;
/* register a task (it will deregister itself when done) */
++m->numCreateDiffTasks;
}
{
if (aWait)
else
}
delete pTask;
return rc;
}
/**
* Prepares this (source) medium, target medium and all intermediate media
* for the merge operation.
*
* This method is to be called prior to calling the #mergeTo() to perform
* necessary consistency checks and place involved media to appropriate
* states. If #mergeTo() is not called or fails, the state modifications
* performed by this method must be undone by #cancelMergeTo().
*
* See #mergeTo() for more information about merging.
*
* @param pTarget Target medium.
* @param aMachineId Allowed machine attachment. NULL means do not check.
* @param aSnapshotId Allowed snapshot attachment. NULL or empty UUID means
* do not check.
* @param fLockMedia Flag whether to lock the medium lock list or not.
* If set to false and the medium lock list locking fails
* later you must call #cancelMergeTo().
* @param fMergeForward Resulting merge direction (out).
* @param pParentForTarget New parent for target medium after merge (out).
* @param aChildrenToReparent List of children of the source which will have
* to be reparented to the target after merge (out).
* @param aMediumLockList Medium locking information (out).
*
* @note Locks medium tree for reading. Locks this object, aTarget and all
* intermediate media for writing.
*/
const Guid *aMachineId,
const Guid *aSnapshotId,
bool fLockMedia,
bool &fMergeForward,
{
AutoCaller autoCaller(this);
fMergeForward = false;
try
{
// locking: we need the tree lock first because we access parent pointers
/* more sanity checking and figuring out the merge direction */
fMergeForward = false;
else
{
if (pMedium == this)
fMergeForward = true;
else
{
{
}
tr("Media '%s' and '%s' are unrelated"),
}
}
/* Build the lock list. */
aMediumLockList = new MediumLockList();
if (fMergeForward)
true /* fMediumLockWrite */,
NULL,
else
false /* fMediumLockWrite */,
NULL,
throw rc;
/* Sanity checking, must be after lock list creation as it depends on
* valid medium states. The medium objects must be accessible. Only
* do this if immediate locking is requested, otherwise it fails when
* we construct a medium lock list for an already running VM. Snapshot
* deletion uses this to simplify its life. */
if (fLockMedia)
{
{
if (m->state != MediumState_Created)
throw setStateError();
}
{
throw pTarget->setStateError();
}
}
/* check medium attachment and other sanity conditions */
if (fMergeForward)
{
{
tr("Medium '%s' involved in the merge operation has more than one child medium (%d)"),
}
/* One backreference is only allowed if the machine ID is not empty
* and it matches the machine the medium is attached to (including
* the snapshot ID if not empty). */
&& ( !aMachineId
|| aMachineId->isEmpty()
|| *getFirstMachineBackrefId() != *aMachineId
&& *getFirstMachineBackrefSnapshotId() != *aSnapshotId)))
throw setError(VBOX_E_OBJECT_IN_USE,
tr("Medium '%s' is attached to %d virtual machines"),
if (m->type == MediumType_Immutable)
tr("Medium '%s' is immutable"),
m->strLocationFull.raw());
}
else
{
{
throw setError(VBOX_E_OBJECT_IN_USE,
tr("Medium '%s' involved in the merge operation has more than one child medium (%d)"),
}
tr("Medium '%s' is immutable"),
}
for (pLast = pLastIntermediate;
{
{
throw setError(VBOX_E_OBJECT_IN_USE,
tr("Medium '%s' involved in the merge operation has more than one child medium (%d)"),
}
throw setError(VBOX_E_OBJECT_IN_USE,
tr("Medium '%s' is attached to %d virtual machines"),
}
/* Update medium states appropriately */
if (m->state == MediumState_Created)
{
rc = markForDeletion();
throw rc;
}
else
{
if (fLockMedia)
throw setStateError();
else if ( m->state == MediumState_LockedWrite
|| m->state == MediumState_LockedRead)
{
/* Either mark it for deletiion in locked state or allow
* others to have done so. */
if (m->preLockState == MediumState_Created)
else if (m->preLockState != MediumState_Deleting)
throw setStateError();
}
else
throw setStateError();
}
if (fMergeForward)
{
/* we will need parent to reparent target */
pParentForTarget = m->pParent;
}
else
{
/* we will need to reparent children of the source */
++it)
{
if (fLockMedia)
{
throw rc;
}
}
}
for (pLast = pLastIntermediate;
{
{
throw rc;
}
else
throw pLast->setStateError();
}
/* Tweak the lock list in the backward merge case, as the target
* isn't marked to be locked for writing yet. */
if (!fMergeForward)
{
lockListEnd--;
it != lockListEnd;
++it)
{
{
break;
}
}
}
if (fLockMedia)
{
{
tr("Failed to lock media when merging to '%s'"),
}
}
}
{
delete aMediumLockList;
}
return rc;
}
/**
* Merges this medium to the specified medium which must be either its
* direct ancestor or descendant.
*
* Given this medium is SOURCE and the specified medium is TARGET, we will
* get two variants of the merge operation:
*
* forward merge
* ------------------------->
* [Extra] <- SOURCE <- Intermediate <- TARGET
* Any Del Del LockWr
*
*
* backward merge
* <-------------------------
* TARGET <- Intermediate <- SOURCE <- [Extra]
* LockWr Del Del LockWr
*
* Each diagram shows the involved media on the media chain where
* SOURCE and TARGET belong. Under each medium there is a state value which
* the medium must have at a time of the mergeTo() call.
*
* The media in the square braces may be absent (e.g. when the forward
* operation takes place and SOURCE is the base medium, or when the backward
* merge operation takes place and TARGET is the last child in the chain) but if
* they present they are involved too as shown.
*
* Neither the source medium nor intermediate media may be attached to
* any VM directly or in the snapshot, otherwise this method will assert.
*
* The #prepareMergeTo() method must be called prior to this method to place all
* involved to necessary states and perform other consistency checks.
*
* If @a aWait is @c true then this method will perform the operation on the
* calling thread and will not return to the caller until the operation is
* completed. When this method succeeds, all intermediate medium objects in
* the chain will be uninitialized, the state of the target medium (and all
* involved extra media) will be restored. @a aMediumLockList will not be
* deleted, whether the operation is successful or not. The caller has to do
* this if appropriate. Note that this (source) medium is not uninitialized
* because of possible AutoCaller instances held by the caller of this method
* on the current thread. It's therefore the responsibility of the caller to
* call Medium::uninit() after releasing all callers.
*
* If @a aWait is @c false then this method will create a thread to perform the
* operation asynchronously and will return immediately. If the operation
* succeeds, the thread will uninitialize the source medium object and all
* intermediate medium objects in the chain, reset the state of the target
* medium (and all involved extra media) and delete @a aMediumLockList.
* If the operation fails, the thread will only reset the states of all
* involved media and delete @a aMediumLockList.
*
* When this method fails (regardless of the @a aWait mode), it is a caller's
* responsiblity to undo state changes and delete @a aMediumLockList using
* #cancelMergeTo().
*
* If @a aProgress is not NULL but the object it points to is @c null then a new
* progress object will be created and assigned to @a *aProgress on success,
* otherwise the existing progress object is used. If Progress is NULL, then no
* NULL when @a aWait is @c false (this method will assert in this case).
*
* @param pTarget Target medium.
* @param fMergeForward Merge direction.
* @param pParentForTarget New parent for target medium after merge.
* @param aChildrenToReparent List of children of the source which will have
* to be reparented to the target after merge.
* @param aMediumLockList Medium locking information.
* completion.
* @param aWait @c true if this method should block instead of creating
* an asynchronous thread.
* @param pfNeedsSaveSettings Optional pointer to a bool that must have been initialized to false and that will be set to true
* by this function if the caller should invoke VirtualBox::saveSettings() because the global settings have changed.
* This only works in "wait" mode; otherwise saveSettings gets called automatically by the thread that was created,
* and this parameter is ignored.
*
* @note Locks the tree lock for writing. Locks the media from the chain
* for writing.
*/
bool fMergeForward,
const MediaList &aChildrenToReparent,
bool aWait,
bool *pfNeedsSaveSettings)
{
AutoCaller autoCaller(this);
try
{
{
/* use the existing progress object... */
/* ...but create a new one if it is null */
{
{
}
static_cast<IMedium*>(this),
TRUE /* aCancelable */);
throw rc;
}
}
aWait /* fKeepMediumLockList */);
throw rc;
}
{
if (aWait)
else
}
delete pTask;
return rc;
}
/**
* Undoes what #prepareMergeTo() did. Must be called if #mergeTo() is not
* called or fails. Frees memory occupied by @a aMediumLockList and unlocks
* the medium objects in @a aChildrenToReparent.
*
* @param aChildrenToReparent List of children of the source which will have
* to be reparented to the target after merge.
* @param aMediumLockList Medium locking information.
*
* @note Locks the media from the chain for writing.
*/
{
AutoCaller autoCaller(this);
/* Revert media marked for deletion to previous state. */
it != mediumListEnd;
++it)
{
{
}
}
/* the destructor will do the work */
delete aMediumLockList;
/* unlock the children which had to be reparented */
++it)
{
}
}
////////////////////////////////////////////////////////////////////////////////
//
// Private methods
//
////////////////////////////////////////////////////////////////////////////////
/**
* Performs extra checks if the medium can be closed and returns S_OK in
* this case. Otherwise, returns a respective error message. Called by
* Close() under the medium tree lock and the medium lock.
*
* @note Also reused by Medium::Reset().
*
* @note Caller must hold the media tree write lock!
*/
{
if (getChildren().size() != 0)
return setError(VBOX_E_OBJECT_IN_USE,
tr("Cannot close medium '%s' because it has %d child media"),
return S_OK;
}
/**
* Unregisters this medium with mVirtualBox. Called by close() under the medium tree lock.
*
* This calls either VirtualBox::unregisterImage or VirtualBox::unregisterHardDisk depending
* on the device type of this medium.
*
* @param pfNeedsSaveSettings Optional pointer to a bool that must have been initialized to false and that will be set to true
* by this function if the caller should invoke VirtualBox::saveSettings() because the global settings have changed.
*
* @note Caller must have locked the media tree lock for writing!
*/
{
/* Note that we need to de-associate ourselves from the parent to let
* unregisterHardDisk() properly save the registry */
/* we modify mParent and access children */
if (m->pParent)
deparent();
switch (m->devType)
{
case DeviceType_DVD:
break;
case DeviceType_Floppy:
break;
case DeviceType_HardDisk:
break;
default:
break;
}
{
if (pParentBackup)
{
// re-associate with the parent as we are still relatives in the registry
m->pParent = pParentBackup;
}
}
return rc;
}
/**
* Checks that the format ID is valid and sets it on success.
*
* Note that this method will caller-reference the format object on success!
* This reference must be released somewhere to let the MediumFormat object be
* uninitialized.
*
* @note Must be called from under this object's write lock.
*/
{
/* get the format object first */
{
return setError(E_INVALIDARG,
tr("Invalid medium storage format '%s'"),
/* reference the format permanently to prevent its unexpected
* uninitialization */
/* get properties (preinsert them as keys in the map). Note that the
* map doesn't grow over the object life time since the set of
* properties is meant to be constant. */
++it)
{
}
}
return S_OK;
}
/**
* Returns the last error message collected by the vdErrorCall callback and
* resets it.
*
* The error message is returned prepended with a dot and a space, like this:
* <code>
* ". <error_text> (%Rrc)"
* </code>
* to make it easily appendable to a more general error message. The @c %Rrc
* format string is given @a aVRC as an argument.
*
* If there is no last error message collected by vdErrorCall or if it is a
* null or empty string, then this function returns the following text:
* <code>
* " (%Rrc)"
* </code>
*
* @note Doesn't do any object locking; it is assumed that the caller makes sure
* the callback isn't called by more than one thread at a time.
*
* @param aVRC VBox error code to use when no error message is provided.
*/
{
else
return error;
}
/**
* Error message callback.
*
* Puts the reported error message to the m->vdError field.
*
* @note Doesn't do any object locking; it is assumed that the caller makes sure
* the callback isn't called by more than one thread at a time.
*
* @param pvUser The opaque data passed on container creation.
* @param rc The VBox error code.
* @param RT_SRC_POS_DECL Use RT_SRC_POS.
* @param pszFormat Error message format string.
* @param va Error message arguments.
*/
/*static*/
{
else
}
/* static */
const char * /* pszzValid */)
{
/* we always return true since the only keys we have are those found in
* VDBACKENDINFO */
return true;
}
/* static */
{
return VERR_CFGM_VALUE_NOT_FOUND;
/* we interpret null values as "no value" in Medium */
return VERR_CFGM_VALUE_NOT_FOUND;
return VINF_SUCCESS;
}
/* static */
{
return VERR_CFGM_VALUE_NOT_FOUND;
return VERR_CFGM_NOT_ENOUGH_SPACE;
/* we interpret null values as "no value" in Medium */
return VERR_CFGM_VALUE_NOT_FOUND;
return VINF_SUCCESS;
}
{
if ((fFlags & VD_INTERFACETCPNET_CONNECT_EXTENDED_SELECT) != 0)
return VERR_NOT_SUPPORTED;
if (!pSocketInt)
return VERR_NO_MEMORY;
*pSock = pSocketInt;
return VINF_SUCCESS;
}
{
return VINF_SUCCESS;
}
{
}
{
int rc = VINF_SUCCESS;
return rc;
}
{
}
{
}
DECLCALLBACK(int) Medium::vdTcpRead(VDSOCKET Sock, void *pvBuffer, size_t cbBuffer, size_t *pcbRead)
{
}
{
}
{
}
{
}
{
}
{
}
{
}
/**
* Starts a new thread driven by the appropriate Medium::Task::handler() method.
*
* @note When the task is executed by this method, IProgress::notifyComplete()
* is automatically called for the progress object associated with this
* task when the task is finished to signal the operation completion for
* other threads asynchronously waiting for it.
*/
{
/* Extreme paranoia: The calling thread should not hold the medium
* tree lock or any medium lock. Since there is no separate lock class
* for medium objects be even more strict: no other object locks. */
#endif
/// @todo use a more descriptive task name
"Medium::Task");
if (RT_FAILURE(vrc))
{
delete pTask;
}
return S_OK;
}
/**
* Fix the parent UUID of all children to point to this medium as their
* parent.
*/
{
false /* fMediumLockWrite */,
this,
try
{
try
{
it != lockListEnd;
++it)
{
// open the medium
pMedium->m->vdDiskIfaces);
if (RT_FAILURE(vrc))
throw vrc;
}
++it)
{
/* VD_OPEN_FLAGS_INFO since UUID is wrong yet */
(*it)->m->vdDiskIfaces);
if (RT_FAILURE(vrc))
throw vrc;
if (RT_FAILURE(vrc))
throw vrc;
if (RT_FAILURE(vrc))
throw vrc;
}
}
catch (int aVRC)
{
tr("Could not update medium UUID references to parent '%s' (%s)"),
m->strLocationFull.raw(),
}
}
return rc;
}
/**
* Runs Medium::Task::handler() on the current thread instead of creating
* a new one.
*
* This call implies that it is made on another temporary thread created for
* some asynchronous task. Avoid calling it from a normal thread since the task
* operations are potentially lengthy and will block the calling thread in this
* case.
*
* @note When the task is executed by this method, IProgress::notifyComplete()
* is not called for the progress object associated with this task when
* the task is finished. Instead, the result of the operation is returned
* by this method directly and it's the caller's responsibility to
* complete the progress object in this case.
*/
bool *pfNeedsSaveSettings)
{
/* Extreme paranoia: The calling thread should not hold the medium
* tree lock or any medium lock. Since there is no separate lock class
* for medium objects be even more strict: no other object locks. */
#endif
/* NIL_RTTHREAD indicates synchronous call. */
}
/**
* Implementation code for the "create base" task.
*
* This only gets started from Medium::CreateBaseStorage() and always runs
* asynchronously. As a result, we always save the VirtualBox.xml file when
* we're done here.
*
* @param task
* @return
*/
{
/* these parameters we need after creation */
bool fGenerateUuid = false;
try
{
/* The object may request a specific UUID (through a special form of
* the setLocation() argument). Otherwise we have to generate it */
if (fGenerateUuid)
{
/* VirtualBox::registerHardDisk() will need UUID */
}
| VD_CAP_CREATE_DYNAMIC), E_FAIL);
/* unlock before the potentially lengthy operation */
try
{
/* ensure the directory exists */
throw rc;
NULL,
&geo,
&geo,
NULL,
if (RT_FAILURE(vrc))
throw setError(VBOX_E_FILE_ERROR,
tr("Could not create the medium storage unit '%s'%s"),
unsigned uImageFlags;
if (RT_SUCCESS(vrc))
}
}
{
/* register with mVirtualBox as the last step and move to
* Created state only on success (leaving an orphan file is
* better than breaking media registry consistency) */
bool fNeedsSaveSettings = false;
if (fNeedsSaveSettings)
{
m->pVirtualBox->saveSettings();
}
}
// reenter the lock before changing state
{
m->state = MediumState_Created;
m->logicalSize = logicalSize;
}
else
{
/* back to NotCreated on failure */
m->state = MediumState_NotCreated;
/* reset UUID to prevent it from being reused next time */
if (fGenerateUuid)
}
return rc;
}
/**
* Implementation code for the "create diff" task.
*
* This task always gets started from Medium::createDiffStorage() and can run
* synchronously or asynchronously depending on the "wait" parameter passed to
* that function. If we run synchronously, the caller expects the bool
* *pfNeedsSaveSettings to be set before returning; otherwise (in asynchronous
* mode), we save the settings ourselves.
*
* @param task
* @return
*/
{
bool fNeedsSaveSettings = false;
bool fGenerateUuid = false;
try
{
/* Lock both in {parent,child} order. */
/* The object may request a specific UUID (through a special form of
* the setLocation() argument). Otherwise we have to generate it */
if (fGenerateUuid)
{
/* VirtualBox::registerHardDisk() will need UUID */
}
/* the two media are now protected by their non-default states;
* unlock the media before the potentially lengthy operation */
try
{
/* Open all media in the target chain but the last. */
it != targetListEnd;
++it)
{
/* Skip over the target diff medium */
continue;
/* sanity check */
/* Open all media in appropriate mode. */
pMedium->m->vdDiskIfaces);
if (RT_FAILURE(vrc))
throw setError(VBOX_E_FILE_ERROR,
tr("Could not open the medium storage unit '%s'%s"),
}
/* ensure the target directory exists */
throw rc;
NULL,
pTarget->m->vdDiskIfaces,
if (RT_FAILURE(vrc))
throw setError(VBOX_E_FILE_ERROR,
tr("Could not create the differencing medium storage unit '%s'%s"),
unsigned uImageFlags;
if (RT_SUCCESS(vrc))
}
}
{
/* associate the child with the parent */
/** @todo r=klaus neither target nor base() are locked,
* potential race! */
/* diffs for immutable media are auto-reset by default */
/* register with mVirtualBox as the last step and move to
* Created state only on success (leaving an orphan file is
* better than breaking media registry consistency) */
/* break the parent association on failure to register */
deparent();
}
{
}
else
{
/* back to NotCreated on failure */
/* reset UUID to prevent it from being reused next time */
if (fGenerateUuid)
}
// deregister the task registered in createDiffStorage()
Assert(m->numCreateDiffTasks != 0);
--m->numCreateDiffTasks;
{
if (fNeedsSaveSettings)
{
// save the global settings; for that we should hold only the VirtualBox lock
m->pVirtualBox->saveSettings();
}
}
else
// synchronous mode: report save settings result to caller
/* Note that in sync mode, it's the caller's responsibility to
* unlock the medium. */
return rc;
}
/**
* Implementation code for the "merge" task.
*
* This task always gets started from Medium::mergeTo() and can run
* synchronously or asynchrously depending on the "wait" parameter passed to
* that function. If we run synchronously, the caller expects the bool
* *pfNeedsSaveSettings to be set before returning; otherwise (in asynchronous
* mode), we save the settings ourselves.
*
* @param task
* @return
*/
{
try
{
try
{
// Similar code appears in SessionMachine::onlineMergeMedium, so
// if you make any changes below check whether they are applicable
// in that context as well.
unsigned uTargetIdx = VD_LAST_IMAGE;
unsigned uSourceIdx = VD_LAST_IMAGE;
/* Open all media in the chain. */
unsigned i = 0;
it != lockListEnd;
++it)
{
if (pMedium == this)
uSourceIdx = i;
uTargetIdx = i;
/*
* complex sanity (sane complexity)
*
* The current medium must be in the Deleting (medium is merged)
* or LockedRead (parent medium) state if it is not the target.
* If it is the target it must be in the LockedWrite state.
*/
/*
* Medium must be the target, in the LockedRead state
* or Deleting state where it is not allowed to be attached
* to a virtual machine.
*/
/* The source medium must be in Deleting state. */
unsigned uOpenFlags = VD_OPEN_FLAGS_NORMAL;
/* Open the medium */
pMedium->m->vdDiskIfaces);
if (RT_FAILURE(vrc))
throw vrc;
i++;
}
if (RT_FAILURE(vrc))
throw vrc;
/* update parent UUIDs */
if (!task.mfMergeForward)
{
/* we need to update UUIDs of all source's children
* which cannot be part of the container at once so
* add each one in there individually */
{
++it)
{
/* VD_OPEN_FLAGS_INFO since UUID is wrong yet */
(*it)->m->vdDiskIfaces);
if (RT_FAILURE(vrc))
throw vrc;
if (RT_FAILURE(vrc))
throw vrc;
if (RT_FAILURE(vrc))
throw vrc;
}
}
}
}
catch (int aVRC)
{
throw setError(VBOX_E_FILE_ERROR,
tr("Could not merge the medium '%s' to '%s'%s"),
m->strLocationFull.raw(),
}
}
{
/* all media but the target were successfully deleted by
* VDMerge; reparent the last one and uninitialize deleted media. */
if (task.mfMergeForward)
{
/* first, unregister the target since it may become a base
* medium which needs re-registration */
/* then, reparent it and disconnect the deleted branch at
* both ends (chain->parent() is source's parent) */
{
deparent();
}
/* then, register again */
}
else
{
/* disconnect the deleted branch at the elder end */
targetChild->deparent();
/* reparent source's children and disconnect the deleted
* branch at the younger end */
{
/* obey {parent,child} lock order */
it++)
{
}
}
}
/* unregister and uninitialize all media removed by the merge */
it != lockListEnd;
)
{
/* Create a real copy of the medium pointer, as the medium
* lock deletion below would invalidate the referenced object. */
/* The target and all media not merged (readonly) are skipped */
{
++it;
continue;
}
NULL /*pfNeedsSaveSettings*/);
/* now, uninitialize the deleted medium (note that
* due to the Deleting state, uninit() will not touch
* the parent-child relationship so we need to
* uninitialize each disk individually) */
/* note that the operation initiator medium (which is
* normally also the source medium) is a special case
* -- there is one more caller added by Task to it which
* we must release. Also, if we are in sync mode, the
* caller may still hold an AutoCaller instance for it
* and therefore we cannot uninit() it (it's therefore
* the caller's responsibility) */
if (pMedium == this)
{
}
/* Delete the medium lock list entry, which also releases the
* caller added by MergeChain before uninit() and updates the
* iterator to point to the right place. */
}
}
{
// in asynchronous mode, save settings now
// for that we should hold only the VirtualBox lock
m->pVirtualBox->saveSettings();
}
else
// synchronous mode: report save settings result to caller
*task.m_pfNeedsSaveSettings = true;
{
/* Here we come if either VDMerge() failed (in which case we
* assume that it tried to do everything to make a further
* retry possible -- e.g. not deleted intermediate media
* and so on) or VirtualBox::saveSettings() failed (where we
* should have the original tree but with intermediate storage
* units deleted by VDMerge()). We have to only restore states
* (through the MergeChain dtor) unless we are run synchronously
* in which case it's the responsibility of the caller as stated
* in the mergeTo() docs. The latter also implies that we
* don't own the merge chain, so release it in this case. */
{
}
}
return rc;
}
/**
* Implementation code for the "clone" task.
*
* This only gets started from Medium::CloneTo() and always runs asynchronously.
* As a result, we always save the VirtualBox.xml file when we're done here.
*
* @param task
* @return
*/
{
bool fCreatingTarget = false;
bool fGenerateUuid = false;
try
{
/* Lock all in {parent,child} order. The lock is also used as a
* signal from the task initiator (which releases it only after
* RTThreadCreate()) that we can start the job. */
/* The object may request a specific UUID (through a special form of
* the setLocation() argument). Otherwise we have to generate it */
if (fGenerateUuid)
{
/* VirtualBox::registerHardDisk() will need UUID */
}
try
{
/* Open all media in the source chain. */
it != sourceListEnd;
++it)
{
/* sanity check */
/** Open all media in read-only mode. */
pMedium->m->vdDiskIfaces);
if (RT_FAILURE(vrc))
throw setError(VBOX_E_FILE_ERROR,
tr("Could not open the medium storage unit '%s'%s"),
}
/* unlock before the potentially lengthy operation */
/* ensure the target directory exists */
throw rc;
try
{
/* Open all media in the target chain. */
it != targetListEnd;
++it)
{
/* If the target medium is not created yet there's no
* reason to open it. */
continue;
/* sanity check */
unsigned uOpenFlags = VD_OPEN_FLAGS_NORMAL;
/* Open all media in appropriate mode. */
pMedium->m->vdDiskIfaces);
if (RT_FAILURE(vrc))
throw setError(VBOX_E_FILE_ERROR,
tr("Could not open the medium storage unit '%s'%s"),
}
/** @todo r=klaus target isn't locked, race getting the state */
false,
0,
NULL,
pTarget->m->vdDiskIfaces,
if (RT_FAILURE(vrc))
throw setError(VBOX_E_FILE_ERROR,
tr("Could not create the clone medium '%s'%s"),
unsigned uImageFlags;
if (RT_SUCCESS(vrc))
}
}
}
/* Only do the parent changes for newly created media. */
{
/* we set mParent & children() */
if (pParent)
{
/* associate the clone with the parent and deassociate
* from VirtualBox */
/* register with mVirtualBox as the last step and move to
* Created state only on success (leaving an orphan file is
* better than breaking media registry consistency) */
/* break parent association on failure to register */
}
else
{
/* just register */
}
}
if (fCreatingTarget)
{
{
}
else
{
/* back to NotCreated on failure */
/* reset UUID to prevent it from being reused next time */
if (fGenerateUuid)
}
}
// now, at the end of this task (always asynchronous), save the settings
{
m->pVirtualBox->saveSettings();
}
/* Everything is explicitly unlocked when the task exits,
* as the task destruction also destroys the source chain. */
/* Make sure the source chain is released early. It could happen
* that we get a deadlock in Appliance::Import when Medium::Close
* is called & the source chain is released at the same time. */
return rc;
}
/**
* Implementation code for the "delete" task.
*
* This task always gets started from Medium::deleteStorage() and can run
* synchronously or asynchrously depending on the "wait" parameter passed to
* that function.
*
* @param task
* @return
*/
{
try
{
/* The lock is also used as a signal from the task initiator (which
* releases it only after RTThreadCreate()) that we can start the job */
/* unlock before the potentially lengthy operation */
try
{
m->vdDiskIfaces);
if (RT_SUCCESS(vrc))
if (RT_FAILURE(vrc))
throw setError(VBOX_E_FILE_ERROR,
tr("Could not delete the medium storage unit '%s'%s"),
}
}
/* go to the NotCreated state even on failure since the storage
* may have been already partially deleted and cannot be used any
* more. One will be able to manually re-open the storage if really
* needed to re-register it. */
m->state = MediumState_NotCreated;
/* Reset UUID to prevent Create* from reusing it again */
return rc;
}
/**
* Implementation code for the "reset" task.
*
* This always gets started asynchronously from Medium::Reset().
*
* @param task
* @return
*/
{
try
{
/* The lock is also used as a signal from the task initiator (which
* releases it only after RTThreadCreate()) that we can start the job */
/// the diff contents but the most efficient way will of course be
/// to add a VDResetDiff() API call
/* unlock before the potentially lengthy operation */
try
{
/* Open all media in the target chain but the last. */
it != targetListEnd;
++it)
{
/* sanity check, "this" is checked above */
/* Open all media in appropriate mode. */
pMedium->m->vdDiskIfaces);
if (RT_FAILURE(vrc))
throw setError(VBOX_E_FILE_ERROR,
tr("Could not open the medium storage unit '%s'%s"),
/* Done when we hit the media which should be reset */
if (pMedium == this)
break;
}
/* first, delete the storage unit */
if (RT_FAILURE(vrc))
throw setError(VBOX_E_FILE_ERROR,
tr("Could not delete the medium storage unit '%s'%s"),
/* next, create it again */
m->vdDiskIfaces);
if (RT_FAILURE(vrc))
throw setError(VBOX_E_FILE_ERROR,
tr("Could not open the medium storage unit '%s'%s"),
/// @todo use the same medium variant as before
NULL,
m->vdDiskIfaces,
if (RT_FAILURE(vrc))
throw setError(VBOX_E_FILE_ERROR,
tr("Could not create the differencing medium storage unit '%s'%s"),
unsigned uImageFlags;
if (RT_SUCCESS(vrc))
}
}
m->logicalSize = logicalSize;
{
/* unlock ourselves when done */
}
/* Note that in sync mode, it's the caller's responsibility to
* unlock the medium. */
return rc;
}
/**
* Implementation code for the "compact" task.
*
* @param task
* @return
*/
{
/* Lock all in {parent,child} order. The lock is also used as a
* signal from the task initiator (which releases it only after
* RTThreadCreate()) that we can start the job. */
try
{
try
{
/* Open all media in the chain. */
it != mediumListEnd;
++it)
{
/* sanity check */
if (it == mediumListLast)
else
/* Open all media but last in read-only mode. Do not handle
* shareable media, as compaction and sharing are mutually
* exclusive. */
pMedium->m->vdDiskIfaces);
if (RT_FAILURE(vrc))
throw setError(VBOX_E_FILE_ERROR,
tr("Could not open the medium storage unit '%s'%s"),
}
/* unlock before the potentially lengthy operation */
if (RT_FAILURE(vrc))
{
if (vrc == VERR_NOT_SUPPORTED)
throw setError(VBOX_E_NOT_SUPPORTED,
tr("Compacting is not yet supported for medium '%s'"),
else if (vrc == VERR_NOT_IMPLEMENTED)
tr("Compacting is not implemented, medium '%s'"),
else
throw setError(VBOX_E_FILE_ERROR,
tr("Could not compact medium '%s'%s"),
}
}
}
/* Everything is explicitly unlocked when the task exits,
* as the task destruction also destroys the media chain. */
return rc;
}
/* vi: set tabstop=4 shiftwidth=4 expandtab: */