MachineImplCloneVM.cpp revision 3d61824852e9ce907d1595e501e595d92947c936
/* $Id$ */
/** @file
* Implementation of MachineCloneVM
*/
/*
* Copyright (C) 2011 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 "MachineImplCloneVM.h"
#include "VirtualBoxImpl.h"
#include "MediumImpl.h"
#include "HostImpl.h"
#ifdef DEBUG_poetzsch
#endif
// typedefs
/////////////////////////////////////////////////////////////////////////////
typedef struct
{
} MEDIUMTASK;
typedef struct
{
bool fCreateDiffs;
bool fAttachLinked;
typedef struct
{
// The private class
/////////////////////////////////////////////////////////////////////////////
struct MachineCloneVMPrivate
{
MachineCloneVMPrivate(MachineCloneVM *a_q, ComObjPtr<Machine> &a_pSrcMachine, ComObjPtr<Machine> &a_pTrgMachine, CloneMode_T a_mode, const RTCList<CloneOptions_T> &opts)
, p(a_pSrcMachine)
{}
/* Thread management */
int startWorker()
{
return RTThreadCreate(NULL,
static_cast<void*>(this),
0,
0,
"MachineClone");
}
{
return VINF_SUCCESS;
}
/* Private helper methods */
/* MachineCloneVM::start helper: */
HRESULT createMachineList(const ComPtr<ISnapshot> &pSnapshot, RTCList< ComObjPtr<Machine> > &machineList) const;
inline void updateProgressStats(MEDIUMTASKCHAIN &mtc, bool fAttachLinked, ULONG &uCount, ULONG &uTotalWeight) const;
HRESULT queryMediasForMachineState(const RTCList<ComObjPtr<Machine> > &machineList, bool fAttachLinked, ULONG &uCount, ULONG &uTotalWeight);
HRESULT queryMediasForMachineAndChildStates(const RTCList<ComObjPtr<Machine> > &machineList, bool fAttachLinked, ULONG &uCount, ULONG &uTotalWeight);
HRESULT queryMediasForAllStates(const RTCList<ComObjPtr<Machine> > &machineList, bool fAttachLinked, ULONG &uCount, ULONG &uTotalWeight);
/* MachineCloneVM::run helper: */
bool findSnapshot(const settings::SnapshotsList &snl, const Guid &id, settings::Snapshot &sn) const;
void updateStorageLists(settings::StorageControllersList &sc, const Bstr &bstrOldId, const Bstr &bstrNewId) const;
void updateSnapshotStorageLists(settings::SnapshotsList &sl, const Bstr &bstrOldId, const Bstr &bstrNewId) const;
HRESULT createDifferencingMedium(const ComObjPtr<Medium> &pParent, const Utf8Str &strSnapshotFolder, RTCList<ComObjPtr<Medium> > &newMedia, ComObjPtr<Medium> *ppDiff) const;
/* Private q and parent pointer */
/* Private helper members */
};
HRESULT MachineCloneVMPrivate::createMachineList(const ComPtr<ISnapshot> &pSnapshot, RTCList< ComObjPtr<Machine> > &machineList) const
{
{
}
return rc;
}
void MachineCloneVMPrivate::updateProgressStats(MEDIUMTASKCHAIN &mtc, bool fAttachLinked, ULONG &uCount, ULONG &uTotalWeight) const
{
if (fAttachLinked)
{
/* Implicit diff creation as part of attach is a pretty cheap
* operation, and does only need one operation per attachment. */
++uCount;
}
else
{
/* Currently the copying of diff images involves reading at least
* the biggest parent in the previous chain. So even if the new
* diff image is small in size, it could need some time to create
* it. Adding the biggest size in the chain should balance this a
* little bit more, i.e. the weight is the sum of the data which
* needs to be read and written. */
{
/* Calculate progress data */
++uCount;
/* Save the max size for better weighting of diff image
* creation. */
}
}
}
HRESULT MachineCloneVMPrivate::addSaveState(const ComObjPtr<Machine> &machine, ULONG &uCount, ULONG &uTotalWeight)
{
if (!bstrSrcSaveStatePath.isEmpty())
{
if (RT_FAILURE(vrc))
return p->setError(VBOX_E_IPRT_ERROR, p->tr("Could not query file size of '%s' (%Rrc)"), sst.strSaveStateFile.c_str(), vrc);
/* same rule as above: count both the data which needs to
* be read and written */
++uCount;
}
return S_OK;
}
HRESULT MachineCloneVMPrivate::queryBaseName(const ComPtr<IMedium> &pMedium, Utf8Str &strBaseName) const
{
return rc;
}
HRESULT MachineCloneVMPrivate::queryMediasForMachineState(const RTCList<ComObjPtr<Machine> > &machineList, bool fAttachLinked, ULONG &uCount, ULONG &uTotalWeight)
{
/* This mode is pretty straightforward. We didn't need to know about any
* attached images of the source VM as cloning targets. The IMedium code
* take than care to merge any (possibly) existing parents into the new
* image. */
{
/* If this is the Snapshot Machine we want to clone, we need to
* create a new diff file for the new "current state". */
/* Add all attachments of the different machines to a worker list. */
{
/* Only harddisk's are of interest. */
if (type != DeviceType_HardDisk)
continue;
/* Valid medium attached? */
if (pSrcMedium.isNull())
continue;
/* Create the medium task chain. In this case it will always
* contain one image only. */
/* Refresh the state so that the file size get read. */
/* Save the base name. */
/* Save the current medium, for later cloning. */
if (fAttachLinked)
else
/* Update the progress info. */
/* Append the list of images which have to be cloned. */
}
/* Add the save state files of this machine if there is one. */
}
return rc;
}
HRESULT MachineCloneVMPrivate::queryMediasForMachineAndChildStates(const RTCList<ComObjPtr<Machine> > &machineList, bool fAttachLinked, ULONG &uCount, ULONG &uTotalWeight)
{
/* This is basically a three step approach. First select all medias
* directly or indirectly involved in the clone. Second create a histogram
* of the usage of all that medias. Third select the medias which are
* directly attached or have more than one directly/indirectly used child
* in the new clone. Step one and two are done in the first loop.
*
* Example of the histogram counts after going through 3 attachments from
* bottom to top:
*
* 3
* |
* -> 3
* / \
* 2 1 <-
* /
* -> 2
* / \
* -> 1 1
* \
* 1 <-
*
* Whenever the histogram count is changing compared to the previous one we
* need to include that image in the cloning step (Marked with <-). If we
* start at zero even the directly attached images are automatically
* included.
*
* Note: This still leads to media chains which can have the same medium
* included. This case is handled in "run" and therefor not critical, but
* it leads to wrong progress infos which isn't nice. */
{
/* If this is the Snapshot Machine we want to clone, we need to
* create a new diff file for the new "current state". */
/* Add all attachments (and their parents) of the different
* machines to a worker list. */
{
/* Only harddisk's are of interest. */
if (type != DeviceType_HardDisk)
continue;
/* Valid medium attached? */
if (pSrcMedium.isNull())
continue;
while (!pSrcMedium.isNull())
{
/* Build a histogram of used medias and the parent chain. */
++mediaHist[pSrcMedium];
/* Refresh the state so that the file size get read. */
/* Query next parent. */
}
}
/* Add the save state files of this machine if there is one. */
}
/* Build up the index list of the image chain. Unfortunately we can't do
* that in the previous loop, cause there we go from child -> parent and
* didn't know how many are between. */
{
}
#ifdef DEBUG_poetzsch
/* Print the histogram */
{
}
#endif
/* Go over every medium in the list and check if it either a directly
* attached disk or has more than one children. If so it needs to be
* replicated. Also we have to make sure that any direct or indirect
* children knows of the new parent (which doesn't necessarily mean it
* is a direct children in the source chain). */
{
{
#ifdef DEBUG_poetzsch
#endif
/* Check if there is a "step" in the histogram when going the chain
* upwards. If so, we need this image, cause there is another branch
* from here in the cloned VM. */
{
}
}
/* Make sure we always using the old base name as new base name, even
* if the base is a differencing image in the source VM (with the UUID
* as name). */
/* Update the old medium chain with the updated one. */
/* Update the progress info. */
}
return rc;
}
HRESULT MachineCloneVMPrivate::queryMediasForAllStates(const RTCList<ComObjPtr<Machine> > &machineList, bool fAttachLinked, ULONG &uCount, ULONG &uTotalWeight)
{
/* In this case we create a exact copy of the original VM. This means just
* adding all directly and indirectly attached disk images to the worker
* list. */
{
/* If this is the Snapshot Machine we want to clone, we need to
* create a new diff file for the new "current state". */
/* Add all attachments (and their parents) of the different
* machines to a worker list. */
{
/* Only harddisk's are of interest. */
if (type != DeviceType_HardDisk)
continue;
/* Valid medium attached? */
if (pSrcMedium.isNull())
continue;
/* Build up a child->parent list of this attachment. (Note: we are
* not interested of any child's not attached to this VM. So this
while (!pSrcMedium.isNull())
{
/* Refresh the state so that the file size get read. */
/* Save the current medium, for later cloning. */
/* Query next parent. */
}
/* Update the progress info. */
/* Append the list of images which have to be cloned. */
}
/* Add the save state files of this machine if there is one. */
}
/* Build up the index list of the image chain. Unfortunately we can't do
* that in the previous loop, cause there we go from child -> parent and
* didn't know how many are between. */
{
}
return rc;
}
bool MachineCloneVMPrivate::findSnapshot(const settings::SnapshotsList &snl, const Guid &id, settings::Snapshot &sn) const
{
{
{
return true;
}
{
return true;
}
}
return false;
}
{
{
if ( fNotNAT
continue;
}
}
{
{
}
}
void MachineCloneVMPrivate::updateStorageLists(settings::StorageControllersList &sc, const Bstr &bstrOldId, const Bstr &bstrNewId) const
{
++it3)
{
++it4)
{
{
}
}
}
}
void MachineCloneVMPrivate::updateSnapshotStorageLists(settings::SnapshotsList &sl, const Bstr &bstrOldId, const Bstr &bstrNewId) const
{
++it)
{
}
}
void MachineCloneVMPrivate::updateStateFile(settings::SnapshotsList &snl, const Guid &id, const Utf8Str &strFile) const
{
{
}
}
HRESULT MachineCloneVMPrivate::createDifferencingMedium(const ComObjPtr<Medium> &pParent, const Utf8Str &strSnapshotFolder, RTCList<ComObjPtr<Medium> > &newMedia, ComObjPtr<Medium> *ppDiff) const
{
try
{
diff.createObject();
NULL); /* pllRegistriesThatNeedSaving */
true /* fMediumLockWrite */,
/* this already registers the new diff image */
NULL /* aProgress */,
true /* aWait */,
NULL); // pllRegistriesThatNeedSaving
delete pMediumLockList;
/* Remember created medium. */
}
{
}
catch (...)
{
}
return rc;
}
/* static */
{
/* If canceled by the user tell it to the copy operation. */
if (fCanceled) return VERR_CANCELLED;
/* Set the new process. */
return VINF_SUCCESS;
}
// The public class
/////////////////////////////////////////////////////////////////////////////
MachineCloneVM::MachineCloneVM(ComObjPtr<Machine> pSrcMachine, ComObjPtr<Machine> pTrgMachine, CloneMode_T mode, const RTCList<CloneOptions_T> &opts)
{
}
{
delete d_ptr;
}
{
try
{
/** @todo r=klaus this code cannot deal with someone crazy specifying
* IMachine corresponding to a mutable machine as d->pSrcMachine */
if (d->pSrcMachine->isSessionMachine())
throw E_FAIL;
/* Handle the special case that someone is requesting a _full_ clone
* with all snapshots (and the current state), but uses a snapshot
* machine (and not the current one) as source machine. In this case we
* just replace the source (snapshot) machine with the current machine. */
if ( d->mode == CloneMode_AllStates
&& d->pSrcMachine->isSnapshotMachine())
{
rc = d->pSrcMachine->getVirtualBox()->FindMachine(bstrSrcMachineId.raw(), newSrcMachine.asOutParam());
}
bool fSubtreeIncludesCurrent = false;
if (d->mode == CloneMode_MachineAndChildStates)
{
if (d->pSrcMachine->isSnapshotMachine())
{
/* find machine object for current snapshot of current state */
throw E_FAIL;
throw E_FAIL;
if (pCurrSnapMachine.isNull())
throw E_FAIL;
/* now check if there is a parent chain which leads to the
* snapshot machine defining the subtree. */
{
if (pSnapMachine.isNull())
throw E_FAIL;
if (pSnapMachine == d->pSrcMachine)
{
fSubtreeIncludesCurrent = true;
break;
}
}
}
else
{
/* If the subtree is only the Current State simply use the
* 'machine' case for cloning. It is easier to understand. */
d->mode = CloneMode_MachineState;
}
}
/* Lock the target machine early (so nobody mess around with it in the meantime). */
if (d->pSrcMachine->isSnapshotMachine())
/* Add the current machine and all snapshot machines below this machine
* in a list for further processing. */
/* Include current state? */
if ( d->mode == CloneMode_MachineState
|| d->mode == CloneMode_AllStates)
/* Should be done a depth copy with all child snapshots? */
if ( d->mode == CloneMode_MachineAndChildStates
|| d->mode == CloneMode_AllStates)
{
ULONG cSnapshots = 0;
if (cSnapshots > 0)
{
if (d->mode == CloneMode_MachineAndChildStates)
if (d->mode == CloneMode_MachineAndChildStates)
{
{
if (pCurrState.isNull())
throw E_FAIL;
}
else
{
}
}
}
}
/* We have different approaches for getting the medias which needs to
* be replicated based on the clone mode the user requested (this is
* mostly about the full clone mode).
* MachineState:
* - Only the images which are directly attached to an source VM will
* be cloned. Any parent disks in the original chain will be merged
* into the final cloned disk.
* MachineAndChildStates:
* - In this case we search for images which have more than one
* children in the cloned VM or are directly attached to the new VM.
* All others will be merged into the remaining images which are
* cloned.
* This case is the most complicated one and needs several iterations
* to make sure we are only cloning images which are really
* necessary.
* AllStates:
* - All disks which are directly or indirectly attached to the
* original VM are cloned.
*
* Note: If you change something generic in one of the methods its
* likely that it need to be changed in the others as well! */
switch (d->mode)
{
case CloneMode_MachineState: d->queryMediasForMachineState(machineList, fAttachLinked, uCount, uTotalWeight); break;
case CloneMode_MachineAndChildStates: d->queryMediasForMachineAndChildStates(machineList, fAttachLinked, uCount, uTotalWeight); break;
case CloneMode_AllStates: d->queryMediasForAllStates(machineList, fAttachLinked, uCount, uTotalWeight); break;
}
/* Now create the progress project, so the user knows whats going on. */
true /* fCancellable */,
1);
int vrc = d->startWorker();
if (RT_FAILURE(vrc))
}
{
}
return rc;
}
{
AutoCaller autoCaller(p);
/*
* Todo:
* - What about log files?
*/
/* Where should all the media go? */
try
{
/* Copy all the configuration from this machine to an empty
* configuration dataset. */
/* Reset media registry. */
* with the stuff from the snapshot. */
if (!d->snapshotId.isEmpty())
if (d->mode == CloneMode_MachineState)
{
{
}
/* Remove any hint on snapshots. */
}
else if ( d->mode == CloneMode_MachineAndChildStates
{
if (!d->pOldMachineState.isNull())
{
/* Copy the snapshot data to the current machine. */
/* Current state is under root snapshot. */
/* There will be created a new differencing image based on this
* snapshot. So reset the modified state. */
trgMCF.fCurrentStateModified = false;
}
/* The snapshot will be the root one. */
}
/* Generate new MAC addresses for all machines when not forbidden. */
{
}
/* When the current snapshot folder is absolute we reset it to the
* default relative folder. */
/* Set the new name. */
/* The absolute name of the snapshot folder. */
strTrgSnapshotFolder = Utf8StrFmt("%s%c%s", strTrgMachineFolder.c_str(), RTPATH_DELIMITER, trgMCF.machineUserData.strSnapshotFolder.c_str());
/* Should we rename the disk names. */
/* We need to create a map with the already created medias. This is
* necessary, cause different snapshots could have the same
* cloned a second time, but simply used. */
{
{
rc = d->pProgress->SetNextOperation(BstrFmt(p->tr("Cloning Disk '%ls' ..."), bstrSrcName.raw()).raw(), mt.uWeight);
if (mtc.fAttachLinked)
{
throw E_POINTER;
if (pBase->isReadOnly())
{
/* create the diff under the snapshot medium */
/* diff image has to be used... */
pNewParent = pDiff;
}
else
{
/* Attach the medium directly, as its type is not
* subject to diff creation. */
}
}
else
{
/* Is a clone already there? */
else
{
/* Default format? */
/* Is the source file based? */
{
/* Yes, just use the source format. Otherwise the defaults
* will be used. */
}
if (!fKeepDiskNames)
{
/* Check if we have to use another name. */
/* If the old disk name was in {uuid} format we also
* want the new name in this format, but with the
* updated id of course. If the old disk was called
* like the VM name, we change it to the new VM name.
* For all other disks we rename them with this
* template: "new name-disk1.vdi". */
if (strSrcTest == strOldVMName)
strNewName = Utf8StrFmt("%s%s", trgMCF.machineUserData.strName.c_str(), RTPathExt(Utf8Str(bstrSrcName).c_str()));
{
if (isValidGuid(strSrcTest))
}
else
strNewName = Utf8StrFmt("%s-disk%d%s", trgMCF.machineUserData.strName.c_str(), ++cDisks, RTPathExt(Utf8Str(bstrSrcName).c_str()));
}
/* Check if this medium comes from the snapshot folder, if
* so, put it there in the cloned machine as well.
* Otherwise it goes to the machine folder. */
Utf8Str strFile = Utf8StrFmt("%s%c%s", strTrgMachineFolder.c_str(), RTPATH_DELIMITER, strNewName.c_str());
if ( !bstrSrcPath.isEmpty()
/* Start creating the clone. */
NULL /* llRegistriesThatNeedSaving */);
/* Update the new uuid. */
/* Do the disk cloning. */
/* Wait until the async process has finished. */
/* Check the result of the async process. */
{
/* If the thread of the progress object has an error, then
* retrieve the error info from there, or it'll be lost. */
}
/* Remember created medium. */
/* Get the medium type from the source and set it to the
* new medium. */
/* register the new harddisk */
{
}
/* This medium becomes the parent of the next medium in the
* chain. */
}
}
/* Save the current source medium index as the new parent
* medium index. */
/* Simply increase the target index. */
}
/* update snapshot configuration */
/* create new 'Current State' diff for caller defined place */
if (mtc.fCreateDiffs)
{
throw E_POINTER;
if (pBase->isReadOnly())
{
/* diff image has to be used... */
pNewParent = pDiff;
}
else
{
/* Attach the medium directly, as its type is not
* subject to diff creation. */
}
}
/* update 'Current State' configuration */
}
/* Make sure all disks know of the new machine uuid. We do this last to
* be able to change the medium type above. */
{
{
{
}
}
}
/* Check if a snapshot folder is necessary and if so doesn't already
* exists. */
if ( !d->llSaveStateFiles.isEmpty()
{
if (RT_FAILURE(vrc))
throw p->setError(VBOX_E_IPRT_ERROR,
}
/* Clone all save state files. */
{
const Utf8Str &strTrgSaveState = Utf8StrFmt("%s%c%s", strTrgSnapshotFolder.c_str(), RTPATH_DELIMITER, RTPathFilename(sst.strSaveStateFile.c_str()));
/* Move to next sub-operation. */
rc = d->pProgress->SetNextOperation(BstrFmt(p->tr("Copy save state file '%s' ..."), RTPathFilename(sst.strSaveStateFile.c_str())).raw(), sst.uWeight);
/* Copy the file only if it was not copied already. */
{
int vrc = RTFileCopyEx(sst.strSaveStateFile.c_str(), strTrgSaveState.c_str(), 0, MachineCloneVMPrivate::copyStateFileProgress, &d->pProgress);
if (RT_FAILURE(vrc))
throw p->setError(VBOX_E_IPRT_ERROR,
p->tr("Could not copy state file '%s' to '%s' (%Rrc)"), sst.strSaveStateFile.c_str(), strTrgSaveState.c_str(), vrc);
}
/* Update the path in the configuration either for the current
* machine state or the snapshots. */
else
}
{
rc = d->pProgress->SetNextOperation(BstrFmt(p->tr("Create Machine Clone '%s' ..."), trgMCF.machineUserData.strName.c_str()).raw(), 1);
/* After modifying the new machine config, we can copy the stuff
* over to the new machine. The machine have to be mutable for
* this. */
/* save all VM data */
bool fNeedsGlobalSaveSettings = false;
/* Release all locks */
{
/* save the global settings; for that we should hold only the
* VirtualBox lock */
}
}
/* Any additional machines need saving? */
if (!llRegistriesThatNeedSaving.empty())
{
}
}
{
}
catch (...)
{
}
/* Cleanup on failure (CANCEL also) */
{
int vrc = VINF_SUCCESS;
/* Delete all created files. */
{
if (RT_FAILURE(vrc))
mrc = p->setError(VBOX_E_IPRT_ERROR, p->tr("Could not delete file '%s' (%Rrc)"), newFiles.at(i).c_str(), vrc);
}
/* Delete all already created medias. (Reverse, cause there could be
* parent->child relations.) */
{
true /* aWait */,
NULL /* llRegistriesThatNeedSaving */);
}
/* Delete the snapshot folder when not empty. */
if (!strTrgSnapshotFolder.isEmpty())
/* Delete the machine folder when not empty. */
}
return mrc;
}
void MachineCloneVM::destroy()
{
delete this;
}