MachineImplCloneVM.cpp revision bd5c7d04b57ea9dfea041aad6a07cd8823b4e7c0
/* $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"
// 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 */
HRESULT createMachineList(const ComPtr<ISnapshot> &pSnapshot, RTCList< ComObjPtr<Machine> > &machineList) const;
settings::Snapshot findSnapshot(settings::MachineConfigFile *pMCF, const settings::SnapshotsList &snl, const Guid &id) 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;
/* Private q and parent pointer */
/* Private helper members */
};
HRESULT MachineCloneVMPrivate::createMachineList(const ComPtr<ISnapshot> &pSnapshot, RTCList< ComObjPtr<Machine> > &machineList) const
{
{
}
return rc;
}
settings::Snapshot MachineCloneVMPrivate::findSnapshot(settings::MachineConfigFile *pMCF, const settings::SnapshotsList &snl, const Guid &id) const
{
{
return *it;
}
}
{
{
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
{
{
}
}
/* 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
{
/* 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());
}
/* 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
&& !d->snapshotId.isEmpty())
if (d->mode == CloneMode_MachineAndChildStates)
{
}
}
}
/* Go over every machine and walk over every attachment this machine has. */
{
/* If this is the Snapshot Machine we want to clone, we need to
* create a new diff file for the new "current state". */
bool fCreateDiffs = false;
if (machine == d->pOldMachineState)
fCreateDiffs = true;
/* If we want to create a linked clone just attach the medium
* associated with the snapshot. The rest is taken care of by
* attach already, so no need to duplicate this. */
bool fAttachLinked = false;
fAttachLinked = true;
/* 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
if (d->mode == CloneMode_MachineState)
{
/* Refresh the state so that the file size get read. */
/* Save the current medium, for later cloning. */
if (fAttachLinked)
else
}
else
{
/** @todo r=klaus this puts way too many images in the list
* when cloning a snapshot (sub)tree, which means that more
* images are cloned than necessary. It is just the easiest
* way to get a working VM, as getting the image
* minimum cloning is rather tricky. */
while (!pSrcMedium.isNull())
{
/* Refresh the state so that the file size get read. */
/* Save the current medium, for later cloning. */
/* Query next parent. */
}
}
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. */
}
}
}
if (!bstrSrcSaveStatePath.isEmpty())
{
if (RT_FAILURE(vrc))
throw 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;
}
}
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
{
/* Copy the snapshot data to the current machine. */
/* 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. */
/* Force writing of setting file. */
trgMCF.fCurrentStateModified = true;
/* 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 (pLMedium->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)
{
/* 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()
else
/* Start creating the clone. */
NULL /* llRegistriesThatNeedSaving */);
/* Update the new uuid. */
/* Do the disk cloning. */
progress2.asOutParam());
/* 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. */
}
}
}
/* Create diffs for the last image chain. */
if (mtc.fCreateDiffs)
{
if (pNewParent->isReadOnly())
{
/* diff image has to be used... */
pNewParent = pDiff;
}
else
{
/* Attach the medium directly, as its type is not
* subject to diff creation. */
}
}
/* We have to patch the configuration, so it contains the new
* medium uuid instead of the old one. */
}
/* Make sure all disks know of the new machine uuid. We do this last to
* be able to change the medium type above. */
{
pMedium->addRegistry(d->options.contains(CloneOptions_Link) ? d->pSrcMachine->mData->mUuid : d->pTrgMachine->mData->mUuid, false /* fRecurse */);
}
/* 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. */
}
/* Now save the new configuration to disk. */
{
}
}
{
}
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;
}
const Utf8Str &strSnapshotFolder,
{
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;
}
void MachineCloneVM::destroy()
{
delete this;
}