VirtualBox

source: vbox/trunk/src/VBox/Main/src-server/MachineImplCloneVM.cpp@ 37989

Last change on this file since 37989 was 37985, checked in by vboxsync, 13 years ago

Main/Machine: fix the medium registry logic for linked clones. Previously creating a linked clone of a snapshot of a linked clone didn't result in the right medium registry update.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 41.1 KB
Line 
1/* $Id: MachineImplCloneVM.cpp 37985 2011-07-15 15:04:39Z vboxsync $ */
2/** @file
3 * Implementation of MachineCloneVM
4 */
5
6/*
7 * Copyright (C) 2011 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18#include "MachineImplCloneVM.h"
19
20#include "VirtualBoxImpl.h"
21#include "MediumImpl.h"
22#include "HostImpl.h"
23
24#include <iprt/path.h>
25#include <iprt/dir.h>
26#include <iprt/cpp/utils.h>
27
28#include <VBox/com/list.h>
29#include <VBox/com/MultiResult.h>
30
31// typedefs
32/////////////////////////////////////////////////////////////////////////////
33
34typedef struct
35{
36 ComPtr<IMedium> pMedium;
37 ULONG uWeight;
38} MEDIUMTASK;
39
40typedef struct
41{
42 RTCList<MEDIUMTASK> chain;
43 bool fCreateDiffs;
44 bool fAttachLinked;
45} MEDIUMTASKCHAIN;
46
47typedef struct
48{
49 Guid snapshotUuid;
50 Utf8Str strSaveStateFile;
51 ULONG uWeight;
52} SAVESTATETASK;
53
54// The private class
55/////////////////////////////////////////////////////////////////////////////
56
57struct MachineCloneVMPrivate
58{
59 MachineCloneVMPrivate(MachineCloneVM *a_q, ComObjPtr<Machine> &a_pSrcMachine, ComObjPtr<Machine> &a_pTrgMachine, CloneMode_T a_mode, const RTCList<CloneOptions_T> &opts)
60 : q_ptr(a_q)
61 , p(a_pSrcMachine)
62 , pSrcMachine(a_pSrcMachine)
63 , pTrgMachine(a_pTrgMachine)
64 , mode(a_mode)
65 , options(opts)
66 {}
67
68 /* Thread management */
69 int startWorker()
70 {
71 return RTThreadCreate(NULL,
72 MachineCloneVMPrivate::workerThread,
73 static_cast<void*>(this),
74 0,
75 RTTHREADTYPE_MAIN_WORKER,
76 0,
77 "MachineClone");
78 }
79
80 static int workerThread(RTTHREAD /* Thread */, void *pvUser)
81 {
82 MachineCloneVMPrivate *pTask = static_cast<MachineCloneVMPrivate*>(pvUser);
83 AssertReturn(pTask, VERR_INVALID_POINTER);
84
85 HRESULT rc = pTask->q_ptr->run();
86
87 pTask->pProgress->notifyComplete(rc);
88
89 pTask->q_ptr->destroy();
90
91 return VINF_SUCCESS;
92 }
93
94 /* Private helper methods */
95 HRESULT createMachineList(const ComPtr<ISnapshot> &pSnapshot, RTCList< ComObjPtr<Machine> > &machineList) const;
96 settings::Snapshot findSnapshot(settings::MachineConfigFile *pMCF, const settings::SnapshotsList &snl, const Guid &id) const;
97 void updateMACAddresses(settings::NetworkAdaptersList &nwl) const;
98 void updateMACAddresses(settings::SnapshotsList &sl) const;
99 void updateStorageLists(settings::StorageControllersList &sc, const Bstr &bstrOldId, const Bstr &bstrNewId) const;
100 void updateSnapshotStorageLists(settings::SnapshotsList &sl, const Bstr &bstrOldId, const Bstr &bstrNewId) const;
101 void updateStateFile(settings::SnapshotsList &snl, const Guid &id, const Utf8Str &strFile) const;
102 static int copyStateFileProgress(unsigned uPercentage, void *pvUser);
103
104 /* Private q and parent pointer */
105 MachineCloneVM *q_ptr;
106 ComObjPtr<Machine> p;
107
108 /* Private helper members */
109 ComObjPtr<Machine> pSrcMachine;
110 ComObjPtr<Machine> pTrgMachine;
111 ComPtr<IMachine> pOldMachineState;
112 ComObjPtr<Progress> pProgress;
113 Guid snapshotId;
114 CloneMode_T mode;
115 RTCList<CloneOptions_T> options;
116 RTCList<MEDIUMTASKCHAIN> llMedias;
117 RTCList<SAVESTATETASK> llSaveStateFiles; /* Snapshot UUID -> File path */
118};
119
120HRESULT MachineCloneVMPrivate::createMachineList(const ComPtr<ISnapshot> &pSnapshot, RTCList< ComObjPtr<Machine> > &machineList) const
121{
122 HRESULT rc = S_OK;
123 Bstr name;
124 rc = pSnapshot->COMGETTER(Name)(name.asOutParam());
125 if (FAILED(rc)) return rc;
126
127 ComPtr<IMachine> pMachine;
128 rc = pSnapshot->COMGETTER(Machine)(pMachine.asOutParam());
129 if (FAILED(rc)) return rc;
130 machineList.append((Machine*)(IMachine*)pMachine);
131
132 SafeIfaceArray<ISnapshot> sfaChilds;
133 rc = pSnapshot->COMGETTER(Children)(ComSafeArrayAsOutParam(sfaChilds));
134 if (FAILED(rc)) return rc;
135 for (size_t i = 0; i < sfaChilds.size(); ++i)
136 {
137 rc = createMachineList(sfaChilds[i], machineList);
138 if (FAILED(rc)) return rc;
139 }
140
141 return rc;
142}
143
144settings::Snapshot MachineCloneVMPrivate::findSnapshot(settings::MachineConfigFile *pMCF, const settings::SnapshotsList &snl, const Guid &id) const
145{
146 settings::SnapshotsList::const_iterator it;
147 for (it = snl.begin(); it != snl.end(); ++it)
148 {
149 if (it->uuid == id)
150 return *it;
151 else if (!it->llChildSnapshots.empty())
152 return findSnapshot(pMCF, it->llChildSnapshots, id);
153 }
154 return settings::Snapshot();
155}
156
157void MachineCloneVMPrivate::updateMACAddresses(settings::NetworkAdaptersList &nwl) const
158{
159 const bool fNotNAT = options.contains(CloneOptions_KeepNATMACs);
160 settings::NetworkAdaptersList::iterator it;
161 for (it = nwl.begin(); it != nwl.end(); ++it)
162 {
163 if ( fNotNAT
164 && it->mode == NetworkAttachmentType_NAT)
165 continue;
166 Host::generateMACAddress(it->strMACAddress);
167 }
168}
169
170void MachineCloneVMPrivate::updateMACAddresses(settings::SnapshotsList &sl) const
171{
172 settings::SnapshotsList::iterator it;
173 for (it = sl.begin(); it != sl.end(); ++it)
174 {
175 updateMACAddresses(it->hardware.llNetworkAdapters);
176 if (!it->llChildSnapshots.empty())
177 updateMACAddresses(it->llChildSnapshots);
178 }
179}
180
181void MachineCloneVMPrivate::updateStorageLists(settings::StorageControllersList &sc, const Bstr &bstrOldId, const Bstr &bstrNewId) const
182{
183 settings::StorageControllersList::iterator it3;
184 for (it3 = sc.begin();
185 it3 != sc.end();
186 ++it3)
187 {
188 settings::AttachedDevicesList &llAttachments = it3->llAttachedDevices;
189 settings::AttachedDevicesList::iterator it4;
190 for (it4 = llAttachments.begin();
191 it4 != llAttachments.end();
192 ++it4)
193 {
194 if ( it4->deviceType == DeviceType_HardDisk
195 && it4->uuid == bstrOldId)
196 {
197 it4->uuid = bstrNewId;
198 }
199 }
200 }
201}
202
203void MachineCloneVMPrivate::updateSnapshotStorageLists(settings::SnapshotsList &sl, const Bstr &bstrOldId, const Bstr &bstrNewId) const
204{
205 settings::SnapshotsList::iterator it;
206 for ( it = sl.begin();
207 it != sl.end();
208 ++it)
209 {
210 updateStorageLists(it->storage.llStorageControllers, bstrOldId, bstrNewId);
211 if (!it->llChildSnapshots.empty())
212 updateSnapshotStorageLists(it->llChildSnapshots, bstrOldId, bstrNewId);
213 }
214}
215
216void MachineCloneVMPrivate::updateStateFile(settings::SnapshotsList &snl, const Guid &id, const Utf8Str &strFile) const
217{
218 settings::SnapshotsList::iterator it;
219 for (it = snl.begin(); it != snl.end(); ++it)
220 {
221 if (it->uuid == id)
222 it->strStateFile = strFile;
223 else if (!it->llChildSnapshots.empty())
224 updateStateFile(it->llChildSnapshots, id, strFile);
225 }
226}
227
228/* static */
229int MachineCloneVMPrivate::copyStateFileProgress(unsigned uPercentage, void *pvUser)
230{
231 ComObjPtr<Progress> pProgress = *static_cast< ComObjPtr<Progress>* >(pvUser);
232
233 BOOL fCanceled = false;
234 HRESULT rc = pProgress->COMGETTER(Canceled)(&fCanceled);
235 if (FAILED(rc)) return VERR_GENERAL_FAILURE;
236 /* If canceled by the user tell it to the copy operation. */
237 if (fCanceled) return VERR_CANCELLED;
238 /* Set the new process. */
239 rc = pProgress->SetCurrentOperationProgress(uPercentage);
240 if (FAILED(rc)) return VERR_GENERAL_FAILURE;
241
242 return VINF_SUCCESS;
243}
244
245// The public class
246/////////////////////////////////////////////////////////////////////////////
247
248MachineCloneVM::MachineCloneVM(ComObjPtr<Machine> pSrcMachine, ComObjPtr<Machine> pTrgMachine, CloneMode_T mode, const RTCList<CloneOptions_T> &opts)
249 : d_ptr(new MachineCloneVMPrivate(this, pSrcMachine, pTrgMachine, mode, opts))
250{
251}
252
253MachineCloneVM::~MachineCloneVM()
254{
255 delete d_ptr;
256}
257
258HRESULT MachineCloneVM::start(IProgress **pProgress)
259{
260 DPTR(MachineCloneVM);
261 ComObjPtr<Machine> &p = d->p;
262
263 HRESULT rc;
264 try
265 {
266 /* Handle the special case that someone is requesting a _full_ clone
267 * with all snapshots (and the current state), but uses a snapshot
268 * machine (and not the current one) as source machine. In this case we
269 * just replace the source (snapshot) machine with the current machine. */
270 if ( d->mode == CloneMode_AllStates
271 && d->pSrcMachine->isSnapshotMachine())
272 {
273 Bstr bstrSrcMachineId;
274 rc = d->pSrcMachine->COMGETTER(Id)(bstrSrcMachineId.asOutParam());
275 if (FAILED(rc)) throw rc;
276 ComPtr<IMachine> newSrcMachine;
277 rc = d->pSrcMachine->getVirtualBox()->FindMachine(bstrSrcMachineId.raw(), newSrcMachine.asOutParam());
278 if (FAILED(rc)) throw rc;
279 d->pSrcMachine = (Machine*)(IMachine*)newSrcMachine;
280 }
281
282 /* Lock the target machine early (so nobody mess around with it in the meantime). */
283 AutoWriteLock trgLock(d->pTrgMachine COMMA_LOCKVAL_SRC_POS);
284
285 if (d->pSrcMachine->isSnapshotMachine())
286 d->snapshotId = d->pSrcMachine->getSnapshotId();
287
288 /* Add the current machine and all snapshot machines below this machine
289 * in a list for further processing. */
290 RTCList< ComObjPtr<Machine> > machineList;
291
292 /* Include current state? */
293 if ( d->mode == CloneMode_MachineState
294 || d->mode == CloneMode_AllStates)
295 machineList.append(d->pSrcMachine);
296 /* Should be done a depth copy with all child snapshots? */
297 if ( d->mode == CloneMode_MachineAndChildStates
298 || d->mode == CloneMode_AllStates)
299 {
300 ULONG cSnapshots = 0;
301 rc = d->pSrcMachine->COMGETTER(SnapshotCount)(&cSnapshots);
302 if (FAILED(rc)) throw rc;
303 if (cSnapshots > 0)
304 {
305 Utf8Str id;
306 if ( d->mode == CloneMode_MachineAndChildStates
307 && !d->snapshotId.isEmpty())
308 id = d->snapshotId.toString();
309 ComPtr<ISnapshot> pSnapshot;
310 rc = d->pSrcMachine->FindSnapshot(Bstr(id).raw(), pSnapshot.asOutParam());
311 if (FAILED(rc)) throw rc;
312 rc = d->createMachineList(pSnapshot, machineList);
313 if (FAILED(rc)) throw rc;
314 if (d->mode == CloneMode_MachineAndChildStates)
315 {
316 rc = pSnapshot->COMGETTER(Machine)(d->pOldMachineState.asOutParam());
317 if (FAILED(rc)) throw rc;
318 }
319 }
320 }
321
322 /* Go over every machine and walk over every attachment this machine has. */
323 ULONG uCount = 2; /* One init task and the machine creation. */
324 ULONG uTotalWeight = 2; /* The init task and the machine creation is worth one. */
325 for (size_t i = 0; i < machineList.size(); ++i)
326 {
327 ComObjPtr<Machine> machine = machineList.at(i);
328 /* If this is the Snapshot Machine we want to clone, we need to
329 * create a new diff file for the new "current state". */
330 bool fCreateDiffs = false;
331 if (machine == d->pOldMachineState)
332 fCreateDiffs = true;
333 /* If we want to create a linked clone just attach the medium
334 * associated with the snapshot. The rest is taken care of by
335 * attach already, so no need to duplicate this. */
336 bool fAttachLinked = false;
337 if (d->options.contains(CloneOptions_Link))
338 fAttachLinked = true;
339 SafeIfaceArray<IMediumAttachment> sfaAttachments;
340 rc = machine->COMGETTER(MediumAttachments)(ComSafeArrayAsOutParam(sfaAttachments));
341 if (FAILED(rc)) throw rc;
342 /* Add all attachments (and their parents) of the different
343 * machines to a worker list. */
344 for (size_t a = 0; a < sfaAttachments.size(); ++a)
345 {
346 const ComPtr<IMediumAttachment> &pAtt = sfaAttachments[a];
347 DeviceType_T type;
348 rc = pAtt->COMGETTER(Type)(&type);
349 if (FAILED(rc)) throw rc;
350
351 /* Only harddisk's are of interest. */
352 if (type != DeviceType_HardDisk)
353 continue;
354
355 /* Valid medium attached? */
356 ComPtr<IMedium> pSrcMedium;
357 rc = pAtt->COMGETTER(Medium)(pSrcMedium.asOutParam());
358 if (FAILED(rc)) throw rc;
359 if (pSrcMedium.isNull())
360 continue;
361
362 /* Build up a child->parent list of this attachment. (Note: we are
363 * not interested of any child's not attached to this VM. So this
364 * will not create a full copy of the base/child relationship.) */
365 MEDIUMTASKCHAIN mtc;
366 mtc.fCreateDiffs = fCreateDiffs;
367 mtc.fAttachLinked = fAttachLinked;
368
369 if (d->mode == CloneMode_MachineState)
370 {
371 /* Refresh the state so that the file size get read. */
372 MediumState_T e;
373 rc = pSrcMedium->RefreshState(&e);
374 if (FAILED(rc)) throw rc;
375 LONG64 lSize;
376 rc = pSrcMedium->COMGETTER(Size)(&lSize);
377 if (FAILED(rc)) throw rc;
378
379 /* Save the current medium, for later cloning. */
380 MEDIUMTASK mt;
381 mt.pMedium = pSrcMedium;
382 if (fAttachLinked)
383 mt.uWeight = 0; /* dummy */
384 else
385 mt.uWeight = (lSize + _1M - 1) / _1M;
386 mtc.chain.append(mt);
387 }
388 else
389 {
390 /** @todo r=klaus this puts way too many images in the list
391 * when cloning a snapshot (sub)tree, which means that more
392 * images are cloned than necessary. It is just the easiest
393 * way to get a working VM, as getting the image
394 * parent/child relationships right for only the bare
395 * minimum cloning is rather tricky. */
396 while (!pSrcMedium.isNull())
397 {
398 /* Refresh the state so that the file size get read. */
399 MediumState_T e;
400 rc = pSrcMedium->RefreshState(&e);
401 if (FAILED(rc)) throw rc;
402 LONG64 lSize;
403 rc = pSrcMedium->COMGETTER(Size)(&lSize);
404 if (FAILED(rc)) throw rc;
405
406 /* Save the current medium, for later cloning. */
407 MEDIUMTASK mt;
408 mt.pMedium = pSrcMedium;
409 mt.uWeight = (lSize + _1M - 1) / _1M;
410 mtc.chain.append(mt);
411
412 /* Query next parent. */
413 rc = pSrcMedium->COMGETTER(Parent)(pSrcMedium.asOutParam());
414 if (FAILED(rc)) throw rc;
415 }
416 }
417
418 if (fAttachLinked)
419 {
420 /* Implicit diff creation as part of attach is a pretty cheap
421 * operation, and does only need one operation per attachment. */
422 ++uCount;
423 uTotalWeight += 1; /* 1MB per attachment */
424 }
425 else
426 {
427 /* Currently the copying of diff images involves reading at least
428 * the biggest parent in the previous chain. So even if the new
429 * diff image is small in size, it could need some time to create
430 * it. Adding the biggest size in the chain should balance this a
431 * little bit more, i.e. the weight is the sum of the data which
432 * needs to be read and written. */
433 uint64_t uMaxSize = 0;
434 for (size_t e = mtc.chain.size(); e > 0; --e)
435 {
436 MEDIUMTASK &mt = mtc.chain.at(e - 1);
437 mt.uWeight += uMaxSize;
438
439 /* Calculate progress data */
440 ++uCount;
441 uTotalWeight += mt.uWeight;
442
443 /* Save the max size for better weighting of diff image
444 * creation. */
445 uMaxSize = RT_MAX(uMaxSize, mt.uWeight);
446 }
447 }
448 d->llMedias.append(mtc);
449 }
450 Bstr bstrSrcSaveStatePath;
451 rc = machine->COMGETTER(StateFilePath)(bstrSrcSaveStatePath.asOutParam());
452 if (FAILED(rc)) throw rc;
453 if (!bstrSrcSaveStatePath.isEmpty())
454 {
455 SAVESTATETASK sst;
456 sst.snapshotUuid = machine->getSnapshotId();
457 sst.strSaveStateFile = bstrSrcSaveStatePath;
458 uint64_t cbSize;
459 int vrc = RTFileQuerySize(sst.strSaveStateFile.c_str(), &cbSize);
460 if (RT_FAILURE(vrc))
461 throw p->setError(VBOX_E_IPRT_ERROR, p->tr("Could not query file size of '%s' (%Rrc)"), sst.strSaveStateFile.c_str(), vrc);
462 /* same rule as above: count both the data which needs to
463 * be read and written */
464 sst.uWeight = 2 * (cbSize + _1M - 1) / _1M;
465 d->llSaveStateFiles.append(sst);
466 ++uCount;
467 uTotalWeight += sst.uWeight;
468 }
469 }
470
471 rc = d->pProgress.createObject();
472 if (FAILED(rc)) throw rc;
473 rc = d->pProgress->init(p->getVirtualBox(),
474 static_cast<IMachine*>(d->pSrcMachine) /* aInitiator */,
475 Bstr(p->tr("Cloning Machine")).raw(),
476 true /* fCancellable */,
477 uCount,
478 uTotalWeight,
479 Bstr(p->tr("Initialize Cloning")).raw(),
480 1);
481 if (FAILED(rc)) throw rc;
482
483 int vrc = d->startWorker();
484
485 if (RT_FAILURE(vrc))
486 p->setError(VBOX_E_IPRT_ERROR, "Could not create machine clone thread (%Rrc)", vrc);
487 }
488 catch (HRESULT rc2)
489 {
490 rc = rc2;
491 }
492
493 if (SUCCEEDED(rc))
494 d->pProgress.queryInterfaceTo(pProgress);
495
496 return rc;
497}
498
499HRESULT MachineCloneVM::run()
500{
501 DPTR(MachineCloneVM);
502 ComObjPtr<Machine> &p = d->p;
503
504 AutoCaller autoCaller(p);
505 if (FAILED(autoCaller.rc())) return autoCaller.rc();
506
507 AutoReadLock srcLock(p COMMA_LOCKVAL_SRC_POS);
508 AutoWriteLock trgLock(d->pTrgMachine COMMA_LOCKVAL_SRC_POS);
509
510 HRESULT rc = S_OK;
511
512 /*
513 * Todo:
514 * - What about log files?
515 */
516
517 /* Where should all the media go? */
518 Utf8Str strTrgSnapshotFolder;
519 Utf8Str strTrgMachineFolder = d->pTrgMachine->getSettingsFileFull();
520 strTrgMachineFolder.stripFilename();
521
522 RTCList<ComObjPtr<Medium> > newMedia; /* All created images */
523 RTCList<Utf8Str> newFiles; /* All extra created files (save states, ...) */
524 try
525 {
526 /* Copy all the configuration from this machine to an empty
527 * configuration dataset. */
528 settings::MachineConfigFile trgMCF = *d->pSrcMachine->mData->pMachineConfigFile;
529
530 /* Reset media registry. */
531 trgMCF.mediaRegistry.llHardDisks.clear();
532 /* If we got a valid snapshot id, replace the hardware/storage section
533 * with the stuff from the snapshot. */
534 settings::Snapshot sn;
535 if (!d->snapshotId.isEmpty())
536 sn = d->findSnapshot(&trgMCF, trgMCF.llFirstSnapshot, d->snapshotId);
537
538 if (d->mode == CloneMode_MachineState)
539 {
540 if (!sn.uuid.isEmpty())
541 {
542 trgMCF.hardwareMachine = sn.hardware;
543 trgMCF.storageMachine = sn.storage;
544 }
545
546 /* Remove any hint on snapshots. */
547 trgMCF.llFirstSnapshot.clear();
548 trgMCF.uuidCurrentSnapshot.clear();
549 }
550 else if ( d->mode == CloneMode_MachineAndChildStates
551 && !sn.uuid.isEmpty())
552 {
553 /* Copy the snapshot data to the current machine. */
554 trgMCF.hardwareMachine = sn.hardware;
555 trgMCF.storageMachine = sn.storage;
556
557 /* The snapshot will be the root one. */
558 trgMCF.uuidCurrentSnapshot = sn.uuid;
559 trgMCF.llFirstSnapshot.clear();
560 trgMCF.llFirstSnapshot.push_back(sn);
561 }
562
563 /* Generate new MAC addresses for all machines when not forbidden. */
564 if (!d->options.contains(CloneOptions_KeepAllMACs))
565 {
566 d->updateMACAddresses(trgMCF.hardwareMachine.llNetworkAdapters);
567 d->updateMACAddresses(trgMCF.llFirstSnapshot);
568 }
569
570 /* When the current snapshot folder is absolute we reset it to the
571 * default relative folder. */
572 if (RTPathStartsWithRoot(trgMCF.machineUserData.strSnapshotFolder.c_str()))
573 trgMCF.machineUserData.strSnapshotFolder = "Snapshots";
574 trgMCF.strStateFile = "";
575 /* Force writing of setting file. */
576 trgMCF.fCurrentStateModified = true;
577 /* Set the new name. */
578 const Utf8Str strOldVMName = trgMCF.machineUserData.strName;
579 trgMCF.machineUserData.strName = d->pTrgMachine->mUserData->s.strName;
580 trgMCF.uuid = d->pTrgMachine->mData->mUuid;
581
582 Bstr bstrSrcSnapshotFolder;
583 rc = d->pSrcMachine->COMGETTER(SnapshotFolder)(bstrSrcSnapshotFolder.asOutParam());
584 if (FAILED(rc)) throw rc;
585 /* The absolute name of the snapshot folder. */
586 strTrgSnapshotFolder = Utf8StrFmt("%s%c%s", strTrgMachineFolder.c_str(), RTPATH_DELIMITER, trgMCF.machineUserData.strSnapshotFolder.c_str());
587
588 /* Should we rename the disk names. */
589 bool fKeepDiskNames = d->options.contains(CloneOptions_KeepDiskNames);
590
591 /* We need to create a map with the already created medias. This is
592 * necessary, cause different snapshots could have the same
593 * parents/parent chain. If a medium is in this map already, it isn't
594 * cloned a second time, but simply used. */
595 typedef std::map<Utf8Str, ComObjPtr<Medium> > TStrMediumMap;
596 typedef std::pair<Utf8Str, ComObjPtr<Medium> > TStrMediumPair;
597 TStrMediumMap map;
598 GuidList llRegistriesThatNeedSaving;
599 size_t cDisks = 0;
600 for (size_t i = 0; i < d->llMedias.size(); ++i)
601 {
602 const MEDIUMTASKCHAIN &mtc = d->llMedias.at(i);
603 ComObjPtr<Medium> pNewParent;
604 for (size_t a = mtc.chain.size(); a > 0; --a)
605 {
606 const MEDIUMTASK &mt = mtc.chain.at(a - 1);
607 ComPtr<IMedium> pMedium = mt.pMedium;
608
609 Bstr bstrSrcName;
610 rc = pMedium->COMGETTER(Name)(bstrSrcName.asOutParam());
611 if (FAILED(rc)) throw rc;
612
613 rc = d->pProgress->SetNextOperation(BstrFmt(p->tr("Cloning Disk '%ls' ..."), bstrSrcName.raw()).raw(), mt.uWeight);
614 if (FAILED(rc)) throw rc;
615
616 Bstr bstrSrcId;
617 rc = pMedium->COMGETTER(Id)(bstrSrcId.asOutParam());
618 if (FAILED(rc)) throw rc;
619
620 if (mtc.fAttachLinked)
621 {
622 IMedium *pTmp = pMedium;
623 ComObjPtr<Medium> pLMedium = static_cast<Medium*>(pTmp);
624 if (pLMedium.isNull())
625 throw E_POINTER;
626 if (pLMedium->isReadOnly())
627 {
628 ComObjPtr<Medium> pDiff;
629 /* create the diff under the snapshot medium */
630 rc = createDiffHelper(pLMedium, strTrgSnapshotFolder,
631 &newMedia, &pDiff);
632 if (FAILED(rc)) throw rc;
633 map.insert(TStrMediumPair(Utf8Str(bstrSrcId), pDiff));
634 /* diff image has to be used... */
635 pNewParent = pDiff;
636 }
637 else
638 {
639 /* Attach the medium directly, as its type is not
640 * subject to diff creation. */
641 newMedia.append(pLMedium);
642 map.insert(TStrMediumPair(Utf8Str(bstrSrcId), pLMedium));
643 pNewParent = pLMedium;
644 }
645 }
646 else
647 {
648 /* Is a clone already there? */
649 TStrMediumMap::iterator it = map.find(Utf8Str(bstrSrcId));
650 if (it != map.end())
651 pNewParent = it->second;
652 else
653 {
654 ComPtr<IMediumFormat> pSrcFormat;
655 rc = pMedium->COMGETTER(MediumFormat)(pSrcFormat.asOutParam());
656 ULONG uSrcCaps = 0;
657 rc = pSrcFormat->COMGETTER(Capabilities)(&uSrcCaps);
658 if (FAILED(rc)) throw rc;
659
660 /* Default format? */
661 Utf8Str strDefaultFormat;
662 p->mParent->getDefaultHardDiskFormat(strDefaultFormat);
663 Bstr bstrSrcFormat(strDefaultFormat);
664 ULONG srcVar = MediumVariant_Standard;
665 /* Is the source file based? */
666 if ((uSrcCaps & MediumFormatCapabilities_File) == MediumFormatCapabilities_File)
667 {
668 /* Yes, just use the source format. Otherwise the defaults
669 * will be used. */
670 rc = pMedium->COMGETTER(Format)(bstrSrcFormat.asOutParam());
671 if (FAILED(rc)) throw rc;
672 rc = pMedium->COMGETTER(Variant)(&srcVar);
673 if (FAILED(rc)) throw rc;
674 }
675
676 Guid newId;
677 newId.create();
678 Utf8Str strNewName(bstrSrcName);
679 if (!fKeepDiskNames)
680 {
681 /* If the old disk name was in {uuid} format we also
682 * want the new name in this format, but with the
683 * updated id of course. If the old disk was called
684 * like the VM name, we change it to the new VM name.
685 * For all other disks we rename them with this
686 * template: "new name-disk1.vdi". */
687 Utf8Str strSrcTest = Utf8Str(bstrSrcName).stripExt();
688 if (strSrcTest == strOldVMName)
689 strNewName = Utf8StrFmt("%s%s", trgMCF.machineUserData.strName.c_str(), RTPathExt(Utf8Str(bstrSrcName).c_str()));
690 else if ( strSrcTest.startsWith("{")
691 && strSrcTest.endsWith("}"))
692 {
693 strSrcTest = strSrcTest.substr(1, strSrcTest.length() - 2);
694 if (isValidGuid(strSrcTest))
695 strNewName = Utf8StrFmt("%s%s", newId.toStringCurly().c_str(), RTPathExt(strNewName.c_str()));
696 }
697 else
698 strNewName = Utf8StrFmt("%s-disk%d%s", trgMCF.machineUserData.strName.c_str(), ++cDisks, RTPathExt(Utf8Str(bstrSrcName).c_str()));
699 }
700
701 /* Check if this medium comes from the snapshot folder, if
702 * so, put it there in the cloned machine as well.
703 * Otherwise it goes to the machine folder. */
704 Bstr bstrSrcPath;
705 Utf8Str strFile = Utf8StrFmt("%s%c%s", strTrgMachineFolder.c_str(), RTPATH_DELIMITER, strNewName.c_str());
706 rc = pMedium->COMGETTER(Location)(bstrSrcPath.asOutParam());
707 if (FAILED(rc)) throw rc;
708 if ( !bstrSrcPath.isEmpty()
709 && RTPathStartsWith(Utf8Str(bstrSrcPath).c_str(), Utf8Str(bstrSrcSnapshotFolder).c_str()))
710 strFile = Utf8StrFmt("%s%c%s", strTrgSnapshotFolder.c_str(), RTPATH_DELIMITER, strNewName.c_str());
711 else
712 strFile = Utf8StrFmt("%s%c%s", strTrgMachineFolder.c_str(), RTPATH_DELIMITER, strNewName.c_str());
713
714 /* Start creating the clone. */
715 ComObjPtr<Medium> pTarget;
716 rc = pTarget.createObject();
717 if (FAILED(rc)) throw rc;
718
719 rc = pTarget->init(p->mParent,
720 Utf8Str(bstrSrcFormat),
721 strFile,
722 Guid::Empty, /* empty media registry */
723 NULL /* llRegistriesThatNeedSaving */);
724 if (FAILED(rc)) throw rc;
725
726 /* Update the new uuid. */
727 pTarget->updateId(newId);
728
729 srcLock.release();
730 /* Do the disk cloning. */
731 ComPtr<IProgress> progress2;
732 rc = pMedium->CloneTo(pTarget,
733 srcVar,
734 pNewParent,
735 progress2.asOutParam());
736 if (FAILED(rc)) throw rc;
737
738 /* Wait until the async process has finished. */
739 rc = d->pProgress->WaitForAsyncProgressCompletion(progress2);
740 srcLock.acquire();
741 if (FAILED(rc)) throw rc;
742
743 /* Check the result of the async process. */
744 LONG iRc;
745 rc = progress2->COMGETTER(ResultCode)(&iRc);
746 if (FAILED(rc)) throw rc;
747 if (FAILED(iRc))
748 {
749 /* If the thread of the progress object has an error, then
750 * retrieve the error info from there, or it'll be lost. */
751 ProgressErrorInfo info(progress2);
752 throw p->setError(iRc, Utf8Str(info.getText()).c_str());
753 }
754 /* Remember created medium. */
755 newMedia.append(pTarget);
756 /* Get the medium type from the source and set it to the
757 * new medium. */
758 MediumType_T type;
759 rc = pMedium->COMGETTER(Type)(&type);
760 if (FAILED(rc)) throw rc;
761 rc = pTarget->COMSETTER(Type)(type);
762 if (FAILED(rc)) throw rc;
763 map.insert(TStrMediumPair(Utf8Str(bstrSrcId), pTarget));
764 /* register the new harddisk */
765 {
766 AutoWriteLock tlock(p->mParent->getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
767 rc = p->mParent->registerHardDisk(pTarget, NULL /* pllRegistriesThatNeedSaving */);
768 if (FAILED(rc)) throw rc;
769 }
770 /* This medium becomes the parent of the next medium in the
771 * chain. */
772 pNewParent = pTarget;
773 }
774 }
775 }
776
777 /* Create diffs for the last image chain. */
778 if (mtc.fCreateDiffs)
779 {
780 if (pNewParent->isReadOnly())
781 {
782 ComObjPtr<Medium> pDiff;
783 rc = createDiffHelper(pNewParent, strTrgSnapshotFolder,
784 &newMedia, &pDiff);
785 if (FAILED(rc)) throw rc;
786 /* diff image has to be used... */
787 pNewParent = pDiff;
788 }
789 else
790 {
791 /* Attach the medium directly, as its type is not
792 * subject to diff creation. */
793 newMedia.append(pNewParent);
794 }
795 }
796 Bstr bstrSrcId;
797 rc = mtc.chain.first().pMedium->COMGETTER(Id)(bstrSrcId.asOutParam());
798 if (FAILED(rc)) throw rc;
799 Bstr bstrTrgId;
800 rc = pNewParent->COMGETTER(Id)(bstrTrgId.asOutParam());
801 if (FAILED(rc)) throw rc;
802 /* We have to patch the configuration, so it contains the new
803 * medium uuid instead of the old one. */
804 d->updateStorageLists(trgMCF.storageMachine.llStorageControllers, bstrSrcId, bstrTrgId);
805 d->updateSnapshotStorageLists(trgMCF.llFirstSnapshot, bstrSrcId, bstrTrgId);
806 }
807 /* Make sure all disks know of the new machine uuid. We do this last to
808 * be able to change the medium type above. */
809 for (size_t i = newMedia.size(); i > 0; --i)
810 {
811 const ComObjPtr<Medium> &pMedium = newMedia.at(i - 1);
812 AutoCaller mac(pMedium);
813 if (FAILED(mac.rc())) throw mac.rc();
814 AutoWriteLock mlock(pMedium COMMA_LOCKVAL_SRC_POS);
815 Guid uuid = d->pTrgMachine->mData->mUuid;
816 if (d->options.contains(CloneOptions_Link))
817 {
818 ComObjPtr<Medium> pParent = pMedium->getParent();
819 mlock.release();
820 if (!pParent.isNull())
821 {
822 AutoCaller mac2(pParent);
823 if (FAILED(mac2.rc())) throw mac2.rc();
824 AutoReadLock mlock2(pParent COMMA_LOCKVAL_SRC_POS);
825 if (pParent->getFirstRegistryMachineId(uuid))
826 VirtualBox::addGuidToListUniquely(llRegistriesThatNeedSaving, uuid);
827 }
828 mlock.acquire();
829 }
830 pMedium->addRegistry(uuid, false /* fRecurse */);
831 }
832 /* Check if a snapshot folder is necessary and if so doesn't already
833 * exists. */
834 if ( !d->llSaveStateFiles.isEmpty()
835 && !RTDirExists(strTrgSnapshotFolder.c_str()))
836 {
837 int vrc = RTDirCreateFullPath(strTrgSnapshotFolder.c_str(), 0777);
838 if (RT_FAILURE(vrc))
839 throw p->setError(VBOX_E_IPRT_ERROR,
840 p->tr("Could not create snapshots folder '%s' (%Rrc)"), strTrgSnapshotFolder.c_str(), vrc);
841 }
842 /* Clone all save state files. */
843 for (size_t i = 0; i < d->llSaveStateFiles.size(); ++i)
844 {
845 SAVESTATETASK sst = d->llSaveStateFiles.at(i);
846 const Utf8Str &strTrgSaveState = Utf8StrFmt("%s%c%s", strTrgSnapshotFolder.c_str(), RTPATH_DELIMITER, RTPathFilename(sst.strSaveStateFile.c_str()));
847
848 /* Move to next sub-operation. */
849 rc = d->pProgress->SetNextOperation(BstrFmt(p->tr("Copy save state file '%s' ..."), RTPathFilename(sst.strSaveStateFile.c_str())).raw(), sst.uWeight);
850 if (FAILED(rc)) throw rc;
851 /* Copy the file only if it was not copied already. */
852 if (!newFiles.contains(strTrgSaveState.c_str()))
853 {
854 int vrc = RTFileCopyEx(sst.strSaveStateFile.c_str(), strTrgSaveState.c_str(), 0, MachineCloneVMPrivate::copyStateFileProgress, &d->pProgress);
855 if (RT_FAILURE(vrc))
856 throw p->setError(VBOX_E_IPRT_ERROR,
857 p->tr("Could not copy state file '%s' to '%s' (%Rrc)"), sst.strSaveStateFile.c_str(), strTrgSaveState.c_str(), vrc);
858 newFiles.append(strTrgSaveState);
859 }
860 /* Update the path in the configuration either for the current
861 * machine state or the snapshots. */
862 if (sst.snapshotUuid.isEmpty())
863 trgMCF.strStateFile = strTrgSaveState;
864 else
865 d->updateStateFile(trgMCF.llFirstSnapshot, sst.snapshotUuid, strTrgSaveState);
866 }
867
868 {
869 rc = d->pProgress->SetNextOperation(BstrFmt(p->tr("Create Machine Clone '%s' ..."), trgMCF.machineUserData.strName.c_str()).raw(), 1);
870 if (FAILED(rc)) throw rc;
871 /* After modifying the new machine config, we can copy the stuff
872 * over to the new machine. The machine have to be mutable for
873 * this. */
874 rc = d->pTrgMachine->checkStateDependency(p->MutableStateDep);
875 if (FAILED(rc)) throw rc;
876 rc = d->pTrgMachine->loadMachineDataFromSettings(trgMCF,
877 &d->pTrgMachine->mData->mUuid);
878 if (FAILED(rc)) throw rc;
879 }
880
881 /* Now save the new configuration to disk. */
882 rc = d->pTrgMachine->SaveSettings();
883 if (FAILED(rc)) throw rc;
884 trgLock.release();
885 if (!llRegistriesThatNeedSaving.empty())
886 {
887 srcLock.release();
888 rc = p->mParent->saveRegistries(llRegistriesThatNeedSaving);
889 if (FAILED(rc)) throw rc;
890 }
891 }
892 catch (HRESULT rc2)
893 {
894 rc = rc2;
895 }
896 catch (...)
897 {
898 rc = VirtualBox::handleUnexpectedExceptions(RT_SRC_POS);
899 }
900
901 MultiResult mrc(rc);
902 /* Cleanup on failure (CANCEL also) */
903 if (FAILED(rc))
904 {
905 int vrc = VINF_SUCCESS;
906 /* Delete all created files. */
907 for (size_t i = 0; i < newFiles.size(); ++i)
908 {
909 vrc = RTFileDelete(newFiles.at(i).c_str());
910 if (RT_FAILURE(vrc))
911 mrc = p->setError(VBOX_E_IPRT_ERROR, p->tr("Could not delete file '%s' (%Rrc)"), newFiles.at(i).c_str(), vrc);
912 }
913 /* Delete all already created medias. (Reverse, cause there could be
914 * parent->child relations.) */
915 for (size_t i = newMedia.size(); i > 0; --i)
916 {
917 const ComObjPtr<Medium> &pMedium = newMedia.at(i - 1);
918 mrc = pMedium->deleteStorage(NULL /* aProgress */,
919 true /* aWait */,
920 NULL /* llRegistriesThatNeedSaving */);
921 pMedium->Close();
922 }
923 /* Delete the snapshot folder when not empty. */
924 if (!strTrgSnapshotFolder.isEmpty())
925 RTDirRemove(strTrgSnapshotFolder.c_str());
926 /* Delete the machine folder when not empty. */
927 RTDirRemove(strTrgMachineFolder.c_str());
928 }
929
930 return mrc;
931}
932
933HRESULT MachineCloneVM::createDiffHelper(const ComObjPtr<Medium> &pParent,
934 const Utf8Str &strSnapshotFolder,
935 RTCList< ComObjPtr<Medium> > *pNewMedia,
936 ComObjPtr<Medium> *ppDiff)
937{
938 DPTR(MachineCloneVM);
939 ComObjPtr<Machine> &p = d->p;
940 HRESULT rc = S_OK;
941
942 try
943 {
944 Bstr bstrSrcId;
945 rc = pParent->COMGETTER(Id)(bstrSrcId.asOutParam());
946 if (FAILED(rc)) throw rc;
947 ComObjPtr<Medium> diff;
948 diff.createObject();
949 rc = diff->init(p->mParent,
950 pParent->getPreferredDiffFormat(),
951 Utf8StrFmt("%s%c", strSnapshotFolder.c_str(), RTPATH_DELIMITER),
952 Guid::Empty, /* empty media registry */
953 NULL); /* pllRegistriesThatNeedSaving */
954 if (FAILED(rc)) throw rc;
955 MediumLockList *pMediumLockList(new MediumLockList());
956 rc = diff->createMediumLockList(true /* fFailIfInaccessible */,
957 true /* fMediumLockWrite */,
958 pParent,
959 *pMediumLockList);
960 if (FAILED(rc)) throw rc;
961 rc = pMediumLockList->Lock();
962 if (FAILED(rc)) throw rc;
963 /* this already registers the new diff image */
964 rc = pParent->createDiffStorage(diff, MediumVariant_Standard,
965 pMediumLockList,
966 NULL /* aProgress */,
967 true /* aWait */,
968 NULL); // pllRegistriesThatNeedSaving
969 delete pMediumLockList;
970 if (FAILED(rc)) throw rc;
971 /* Remember created medium. */
972 pNewMedia->append(diff);
973 *ppDiff = diff;
974 }
975 catch (HRESULT rc2)
976 {
977 rc = rc2;
978 }
979 catch (...)
980 {
981 rc = VirtualBox::handleUnexpectedExceptions(RT_SRC_POS);
982 }
983
984 return rc;
985}
986
987void MachineCloneVM::destroy()
988{
989 delete this;
990}
991
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette