VirtualBox

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

Last change on this file since 48955 was 48955, checked in by vboxsync, 11 years ago

Main: Whitespace (including tabs!) and svn:keywords cleanups by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 59.2 KB
Line 
1/* $Id: MachineImplCloneVM.cpp 48955 2013-10-07 21:59:25Z vboxsync $ */
2/** @file
3 * Implementation of MachineCloneVM
4 */
5
6/*
7 * Copyright (C) 2011-2013 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#ifdef DEBUG_poetzsch
28# include <iprt/stream.h>
29#endif
30
31#include <VBox/com/list.h>
32#include <VBox/com/MultiResult.h>
33
34// typedefs
35/////////////////////////////////////////////////////////////////////////////
36
37typedef struct
38{
39 Utf8Str strBaseName;
40 ComPtr<IMedium> pMedium;
41 uint32_t uIdx;
42 ULONG uWeight;
43} MEDIUMTASK;
44
45typedef struct
46{
47 RTCList<MEDIUMTASK> chain;
48 bool fCreateDiffs;
49 bool fAttachLinked;
50} MEDIUMTASKCHAIN;
51
52typedef struct
53{
54 Guid snapshotUuid;
55 Utf8Str strSaveStateFile;
56 ULONG uWeight;
57} SAVESTATETASK;
58
59// The private class
60/////////////////////////////////////////////////////////////////////////////
61
62struct MachineCloneVMPrivate
63{
64 MachineCloneVMPrivate(MachineCloneVM *a_q, ComObjPtr<Machine> &a_pSrcMachine, ComObjPtr<Machine> &a_pTrgMachine, CloneMode_T a_mode, const RTCList<CloneOptions_T> &opts)
65 : q_ptr(a_q)
66 , p(a_pSrcMachine)
67 , pSrcMachine(a_pSrcMachine)
68 , pTrgMachine(a_pTrgMachine)
69 , mode(a_mode)
70 , options(opts)
71 {}
72
73 /* Thread management */
74 int startWorker()
75 {
76 return RTThreadCreate(NULL,
77 MachineCloneVMPrivate::workerThread,
78 static_cast<void*>(this),
79 0,
80 RTTHREADTYPE_MAIN_WORKER,
81 0,
82 "MachineClone");
83 }
84
85 static int workerThread(RTTHREAD /* Thread */, void *pvUser)
86 {
87 MachineCloneVMPrivate *pTask = static_cast<MachineCloneVMPrivate*>(pvUser);
88 AssertReturn(pTask, VERR_INVALID_POINTER);
89
90 HRESULT rc = pTask->q_ptr->run();
91
92 pTask->pProgress->notifyComplete(rc);
93
94 pTask->q_ptr->destroy();
95
96 return VINF_SUCCESS;
97 }
98
99 /* Private helper methods */
100
101 /* MachineCloneVM::start helper: */
102 HRESULT createMachineList(const ComPtr<ISnapshot> &pSnapshot, RTCList< ComObjPtr<Machine> > &machineList) const;
103 inline void updateProgressStats(MEDIUMTASKCHAIN &mtc, bool fAttachLinked, ULONG &uCount, ULONG &uTotalWeight) const;
104 inline HRESULT addSaveState(const ComObjPtr<Machine> &machine, ULONG &uCount, ULONG &uTotalWeight);
105 inline HRESULT queryBaseName(const ComPtr<IMedium> &pMedium, Utf8Str &strBaseName) const;
106 HRESULT queryMediasForMachineState(const RTCList<ComObjPtr<Machine> > &machineList, bool fAttachLinked, ULONG &uCount, ULONG &uTotalWeight);
107 HRESULT queryMediasForMachineAndChildStates(const RTCList<ComObjPtr<Machine> > &machineList, bool fAttachLinked, ULONG &uCount, ULONG &uTotalWeight);
108 HRESULT queryMediasForAllStates(const RTCList<ComObjPtr<Machine> > &machineList, bool fAttachLinked, ULONG &uCount, ULONG &uTotalWeight);
109
110 /* MachineCloneVM::run helper: */
111 bool findSnapshot(const settings::SnapshotsList &snl, const Guid &id, settings::Snapshot &sn) const;
112 void updateMACAddresses(settings::NetworkAdaptersList &nwl) const;
113 void updateMACAddresses(settings::SnapshotsList &sl) const;
114 void updateStorageLists(settings::StorageControllersList &sc, const Bstr &bstrOldId, const Bstr &bstrNewId) const;
115 void updateSnapshotStorageLists(settings::SnapshotsList &sl, const Bstr &bstrOldId, const Bstr &bstrNewId) const;
116 void updateStateFile(settings::SnapshotsList &snl, const Guid &id, const Utf8Str &strFile) const;
117 HRESULT createDifferencingMedium(const ComObjPtr<Machine> &pMachine, const ComObjPtr<Medium> &pParent, const Utf8Str &strSnapshotFolder, RTCList<ComObjPtr<Medium> > &newMedia, ComObjPtr<Medium> *ppDiff) const;
118 static int copyStateFileProgress(unsigned uPercentage, void *pvUser);
119
120 /* Private q and parent pointer */
121 MachineCloneVM *q_ptr;
122 ComObjPtr<Machine> p;
123
124 /* Private helper members */
125 ComObjPtr<Machine> pSrcMachine;
126 ComObjPtr<Machine> pTrgMachine;
127 ComPtr<IMachine> pOldMachineState;
128 ComObjPtr<Progress> pProgress;
129 Guid snapshotId;
130 CloneMode_T mode;
131 RTCList<CloneOptions_T> options;
132 RTCList<MEDIUMTASKCHAIN> llMedias;
133 RTCList<SAVESTATETASK> llSaveStateFiles; /* Snapshot UUID -> File path */
134};
135
136HRESULT MachineCloneVMPrivate::createMachineList(const ComPtr<ISnapshot> &pSnapshot, RTCList< ComObjPtr<Machine> > &machineList) const
137{
138 HRESULT rc = S_OK;
139 Bstr name;
140 rc = pSnapshot->COMGETTER(Name)(name.asOutParam());
141 if (FAILED(rc)) return rc;
142
143 ComPtr<IMachine> pMachine;
144 rc = pSnapshot->COMGETTER(Machine)(pMachine.asOutParam());
145 if (FAILED(rc)) return rc;
146 machineList.append((Machine*)(IMachine*)pMachine);
147
148 SafeIfaceArray<ISnapshot> sfaChilds;
149 rc = pSnapshot->COMGETTER(Children)(ComSafeArrayAsOutParam(sfaChilds));
150 if (FAILED(rc)) return rc;
151 for (size_t i = 0; i < sfaChilds.size(); ++i)
152 {
153 rc = createMachineList(sfaChilds[i], machineList);
154 if (FAILED(rc)) return rc;
155 }
156
157 return rc;
158}
159
160void MachineCloneVMPrivate::updateProgressStats(MEDIUMTASKCHAIN &mtc, bool fAttachLinked, ULONG &uCount, ULONG &uTotalWeight) const
161{
162 if (fAttachLinked)
163 {
164 /* Implicit diff creation as part of attach is a pretty cheap
165 * operation, and does only need one operation per attachment. */
166 ++uCount;
167 uTotalWeight += 1; /* 1MB per attachment */
168 }
169 else
170 {
171 /* Currently the copying of diff images involves reading at least
172 * the biggest parent in the previous chain. So even if the new
173 * diff image is small in size, it could need some time to create
174 * it. Adding the biggest size in the chain should balance this a
175 * little bit more, i.e. the weight is the sum of the data which
176 * needs to be read and written. */
177 ULONG uMaxWeight = 0;
178 for (size_t e = mtc.chain.size(); e > 0; --e)
179 {
180 MEDIUMTASK &mt = mtc.chain.at(e - 1);
181 mt.uWeight += uMaxWeight;
182
183 /* Calculate progress data */
184 ++uCount;
185 uTotalWeight += mt.uWeight;
186
187 /* Save the max size for better weighting of diff image
188 * creation. */
189 uMaxWeight = RT_MAX(uMaxWeight, mt.uWeight);
190 }
191 }
192}
193
194HRESULT MachineCloneVMPrivate::addSaveState(const ComObjPtr<Machine> &machine, ULONG &uCount, ULONG &uTotalWeight)
195{
196 Bstr bstrSrcSaveStatePath;
197 HRESULT rc = machine->COMGETTER(StateFilePath)(bstrSrcSaveStatePath.asOutParam());
198 if (FAILED(rc)) return rc;
199 if (!bstrSrcSaveStatePath.isEmpty())
200 {
201 SAVESTATETASK sst;
202 sst.snapshotUuid = machine->getSnapshotId();
203 sst.strSaveStateFile = bstrSrcSaveStatePath;
204 uint64_t cbSize;
205 int vrc = RTFileQuerySize(sst.strSaveStateFile.c_str(), &cbSize);
206 if (RT_FAILURE(vrc))
207 return p->setError(VBOX_E_IPRT_ERROR, p->tr("Could not query file size of '%s' (%Rrc)"), sst.strSaveStateFile.c_str(), vrc);
208 /* same rule as above: count both the data which needs to
209 * be read and written */
210 sst.uWeight = (ULONG)(2 * (cbSize + _1M - 1) / _1M);
211 llSaveStateFiles.append(sst);
212 ++uCount;
213 uTotalWeight += sst.uWeight;
214 }
215 return S_OK;
216}
217
218HRESULT MachineCloneVMPrivate::queryBaseName(const ComPtr<IMedium> &pMedium, Utf8Str &strBaseName) const
219{
220 ComPtr<IMedium> pBaseMedium;
221 HRESULT rc = pMedium->COMGETTER(Base)(pBaseMedium.asOutParam());
222 if (FAILED(rc)) return rc;
223 Bstr bstrBaseName;
224 rc = pBaseMedium->COMGETTER(Name)(bstrBaseName.asOutParam());
225 if (FAILED(rc)) return rc;
226 strBaseName = bstrBaseName;
227 return rc;
228}
229
230HRESULT MachineCloneVMPrivate::queryMediasForMachineState(const RTCList<ComObjPtr<Machine> > &machineList, bool fAttachLinked, ULONG &uCount, ULONG &uTotalWeight)
231{
232 /* This mode is pretty straightforward. We didn't need to know about any
233 * parent/children relationship and therefor simply adding all directly
234 * attached images of the source VM as cloning targets. The IMedium code
235 * take than care to merge any (possibly) existing parents into the new
236 * image. */
237 HRESULT rc = S_OK;
238 for (size_t i = 0; i < machineList.size(); ++i)
239 {
240 const ComObjPtr<Machine> &machine = machineList.at(i);
241 /* If this is the Snapshot Machine we want to clone, we need to
242 * create a new diff file for the new "current state". */
243 const bool fCreateDiffs = (machine == pOldMachineState);
244 /* Add all attachments of the different machines to a worker list. */
245 SafeIfaceArray<IMediumAttachment> sfaAttachments;
246 rc = machine->COMGETTER(MediumAttachments)(ComSafeArrayAsOutParam(sfaAttachments));
247 if (FAILED(rc)) return rc;
248 for (size_t a = 0; a < sfaAttachments.size(); ++a)
249 {
250 const ComPtr<IMediumAttachment> &pAtt = sfaAttachments[a];
251 DeviceType_T type;
252 rc = pAtt->COMGETTER(Type)(&type);
253 if (FAILED(rc)) return rc;
254
255 /* Only harddisk's are of interest. */
256 if (type != DeviceType_HardDisk)
257 continue;
258
259 /* Valid medium attached? */
260 ComPtr<IMedium> pSrcMedium;
261 rc = pAtt->COMGETTER(Medium)(pSrcMedium.asOutParam());
262 if (FAILED(rc)) return rc;
263 if (pSrcMedium.isNull())
264 continue;
265
266 /* Create the medium task chain. In this case it will always
267 * contain one image only. */
268 MEDIUMTASKCHAIN mtc;
269 mtc.fCreateDiffs = fCreateDiffs;
270 mtc.fAttachLinked = fAttachLinked;
271
272 /* Refresh the state so that the file size get read. */
273 MediumState_T e;
274 rc = pSrcMedium->RefreshState(&e);
275 if (FAILED(rc)) return rc;
276 LONG64 lSize;
277 rc = pSrcMedium->COMGETTER(Size)(&lSize);
278 if (FAILED(rc)) return rc;
279
280 MEDIUMTASK mt;
281 mt.uIdx = UINT32_MAX; /* No read/write optimization possible. */
282
283 /* Save the base name. */
284 rc = queryBaseName(pSrcMedium, mt.strBaseName);
285 if (FAILED(rc)) return rc;
286
287 /* Save the current medium, for later cloning. */
288 mt.pMedium = pSrcMedium;
289 if (fAttachLinked)
290 mt.uWeight = 0; /* dummy */
291 else
292 mt.uWeight = (ULONG)((lSize + _1M - 1) / _1M);
293 mtc.chain.append(mt);
294
295 /* Update the progress info. */
296 updateProgressStats(mtc, fAttachLinked, uCount, uTotalWeight);
297 /* Append the list of images which have to be cloned. */
298 llMedias.append(mtc);
299 }
300 /* Add the save state files of this machine if there is one. */
301 rc = addSaveState(machine, uCount, uTotalWeight);
302 if (FAILED(rc)) return rc;
303 }
304
305 return rc;
306}
307
308HRESULT MachineCloneVMPrivate::queryMediasForMachineAndChildStates(const RTCList<ComObjPtr<Machine> > &machineList, bool fAttachLinked, ULONG &uCount, ULONG &uTotalWeight)
309{
310 /* This is basically a three step approach. First select all medias
311 * directly or indirectly involved in the clone. Second create a histogram
312 * of the usage of all that medias. Third select the medias which are
313 * directly attached or have more than one directly/indirectly used child
314 * in the new clone. Step one and two are done in the first loop.
315 *
316 * Example of the histogram counts after going through 3 attachments from
317 * bottom to top:
318 *
319 * 3
320 * |
321 * -> 3
322 * / \
323 * 2 1 <-
324 * /
325 * -> 2
326 * / \
327 * -> 1 1
328 * \
329 * 1 <-
330 *
331 * Whenever the histogram count is changing compared to the previous one we
332 * need to include that image in the cloning step (Marked with <-). If we
333 * start at zero even the directly attached images are automatically
334 * included.
335 *
336 * Note: This still leads to media chains which can have the same medium
337 * included. This case is handled in "run" and therefor not critical, but
338 * it leads to wrong progress infos which isn't nice. */
339
340 HRESULT rc = S_OK;
341 std::map<ComPtr<IMedium>, uint32_t> mediaHist; /* Our usage histogram for the medias */
342 for (size_t i = 0; i < machineList.size(); ++i)
343 {
344 const ComObjPtr<Machine> &machine = machineList.at(i);
345 /* If this is the Snapshot Machine we want to clone, we need to
346 * create a new diff file for the new "current state". */
347 const bool fCreateDiffs = (machine == pOldMachineState);
348 /* Add all attachments (and their parents) of the different
349 * machines to a worker list. */
350 SafeIfaceArray<IMediumAttachment> sfaAttachments;
351 rc = machine->COMGETTER(MediumAttachments)(ComSafeArrayAsOutParam(sfaAttachments));
352 if (FAILED(rc)) return rc;
353 for (size_t a = 0; a < sfaAttachments.size(); ++a)
354 {
355 const ComPtr<IMediumAttachment> &pAtt = sfaAttachments[a];
356 DeviceType_T type;
357 rc = pAtt->COMGETTER(Type)(&type);
358 if (FAILED(rc)) return rc;
359
360 /* Only harddisk's are of interest. */
361 if (type != DeviceType_HardDisk)
362 continue;
363
364 /* Valid medium attached? */
365 ComPtr<IMedium> pSrcMedium;
366 rc = pAtt->COMGETTER(Medium)(pSrcMedium.asOutParam());
367 if (FAILED(rc)) return rc;
368
369 if (pSrcMedium.isNull())
370 continue;
371
372 MEDIUMTASKCHAIN mtc;
373 mtc.fCreateDiffs = fCreateDiffs;
374 mtc.fAttachLinked = fAttachLinked;
375
376 while (!pSrcMedium.isNull())
377 {
378 /* Build a histogram of used medias and the parent chain. */
379 ++mediaHist[pSrcMedium];
380
381 /* Refresh the state so that the file size get read. */
382 MediumState_T e;
383 rc = pSrcMedium->RefreshState(&e);
384 if (FAILED(rc)) return rc;
385 LONG64 lSize;
386 rc = pSrcMedium->COMGETTER(Size)(&lSize);
387 if (FAILED(rc)) return rc;
388
389 MEDIUMTASK mt;
390 mt.uIdx = UINT32_MAX;
391 mt.pMedium = pSrcMedium;
392 mt.uWeight = (ULONG)((lSize + _1M - 1) / _1M);
393 mtc.chain.append(mt);
394
395 /* Query next parent. */
396 rc = pSrcMedium->COMGETTER(Parent)(pSrcMedium.asOutParam());
397 if (FAILED(rc)) return rc;
398 }
399
400 llMedias.append(mtc);
401 }
402 /* Add the save state files of this machine if there is one. */
403 rc = addSaveState(machine, uCount, uTotalWeight);
404 if (FAILED(rc)) return rc;
405 }
406 /* Build up the index list of the image chain. Unfortunately we can't do
407 * that in the previous loop, cause there we go from child -> parent and
408 * didn't know how many are between. */
409 for (size_t i = 0; i < llMedias.size(); ++i)
410 {
411 uint32_t uIdx = 0;
412 MEDIUMTASKCHAIN &mtc = llMedias.at(i);
413 for (size_t a = mtc.chain.size(); a > 0; --a)
414 mtc.chain[a - 1].uIdx = uIdx++;
415 }
416#ifdef DEBUG_poetzsch
417 /* Print the histogram */
418 std::map<ComPtr<IMedium>, uint32_t>::iterator it;
419 for (it = mediaHist.begin(); it != mediaHist.end(); ++it)
420 {
421 Bstr bstrSrcName;
422 rc = (*it).first->COMGETTER(Name)(bstrSrcName.asOutParam());
423 if (FAILED(rc)) return rc;
424 RTPrintf("%ls: %d\n", bstrSrcName.raw(), (*it).second);
425 }
426#endif
427 /* Go over every medium in the list and check if it either a directly
428 * attached disk or has more than one children. If so it needs to be
429 * replicated. Also we have to make sure that any direct or indirect
430 * children knows of the new parent (which doesn't necessarily mean it
431 * is a direct children in the source chain). */
432 for (size_t i = 0; i < llMedias.size(); ++i)
433 {
434 MEDIUMTASKCHAIN &mtc = llMedias.at(i);
435 RTCList<MEDIUMTASK> newChain;
436 uint32_t used = 0;
437 for (size_t a = 0; a < mtc.chain.size(); ++a)
438 {
439 const MEDIUMTASK &mt = mtc.chain.at(a);
440 uint32_t hist = mediaHist[mt.pMedium];
441#ifdef DEBUG_poetzsch
442 Bstr bstrSrcName;
443 rc = mt.pMedium->COMGETTER(Name)(bstrSrcName.asOutParam());
444 if (FAILED(rc)) return rc;
445 RTPrintf("%ls: %d (%d)\n", bstrSrcName.raw(), hist, used);
446#endif
447 /* Check if there is a "step" in the histogram when going the chain
448 * upwards. If so, we need this image, cause there is another branch
449 * from here in the cloned VM. */
450 if (hist > used)
451 {
452 newChain.append(mt);
453 used = hist;
454 }
455 }
456 /* Make sure we always using the old base name as new base name, even
457 * if the base is a differencing image in the source VM (with the UUID
458 * as name). */
459 rc = queryBaseName(newChain.last().pMedium, newChain.last().strBaseName);
460 if (FAILED(rc)) return rc;
461 /* Update the old medium chain with the updated one. */
462 mtc.chain = newChain;
463 /* Update the progress info. */
464 updateProgressStats(mtc, fAttachLinked, uCount, uTotalWeight);
465 }
466
467 return rc;
468}
469
470HRESULT MachineCloneVMPrivate::queryMediasForAllStates(const RTCList<ComObjPtr<Machine> > &machineList, bool fAttachLinked, ULONG &uCount, ULONG &uTotalWeight)
471{
472 /* In this case we create a exact copy of the original VM. This means just
473 * adding all directly and indirectly attached disk images to the worker
474 * list. */
475 HRESULT rc = S_OK;
476 for (size_t i = 0; i < machineList.size(); ++i)
477 {
478 const ComObjPtr<Machine> &machine = machineList.at(i);
479 /* If this is the Snapshot Machine we want to clone, we need to
480 * create a new diff file for the new "current state". */
481 const bool fCreateDiffs = (machine == pOldMachineState);
482 /* Add all attachments (and their parents) of the different
483 * machines to a worker list. */
484 SafeIfaceArray<IMediumAttachment> sfaAttachments;
485 rc = machine->COMGETTER(MediumAttachments)(ComSafeArrayAsOutParam(sfaAttachments));
486 if (FAILED(rc)) return rc;
487 for (size_t a = 0; a < sfaAttachments.size(); ++a)
488 {
489 const ComPtr<IMediumAttachment> &pAtt = sfaAttachments[a];
490 DeviceType_T type;
491 rc = pAtt->COMGETTER(Type)(&type);
492 if (FAILED(rc)) return rc;
493
494 /* Only harddisk's are of interest. */
495 if (type != DeviceType_HardDisk)
496 continue;
497
498 /* Valid medium attached? */
499 ComPtr<IMedium> pSrcMedium;
500 rc = pAtt->COMGETTER(Medium)(pSrcMedium.asOutParam());
501 if (FAILED(rc)) return rc;
502 if (pSrcMedium.isNull())
503 continue;
504
505 /* Build up a child->parent list of this attachment. (Note: we are
506 * not interested of any child's not attached to this VM. So this
507 * will not create a full copy of the base/child relationship.) */
508 MEDIUMTASKCHAIN mtc;
509 mtc.fCreateDiffs = fCreateDiffs;
510 mtc.fAttachLinked = fAttachLinked;
511
512 while (!pSrcMedium.isNull())
513 {
514 /* Refresh the state so that the file size get read. */
515 MediumState_T e;
516 rc = pSrcMedium->RefreshState(&e);
517 if (FAILED(rc)) return rc;
518 LONG64 lSize;
519 rc = pSrcMedium->COMGETTER(Size)(&lSize);
520 if (FAILED(rc)) return rc;
521
522 /* Save the current medium, for later cloning. */
523 MEDIUMTASK mt;
524 mt.uIdx = UINT32_MAX;
525 mt.pMedium = pSrcMedium;
526 mt.uWeight = (ULONG)((lSize + _1M - 1) / _1M);
527 mtc.chain.append(mt);
528
529 /* Query next parent. */
530 rc = pSrcMedium->COMGETTER(Parent)(pSrcMedium.asOutParam());
531 if (FAILED(rc)) return rc;
532 }
533 /* Update the progress info. */
534 updateProgressStats(mtc, fAttachLinked, uCount, uTotalWeight);
535 /* Append the list of images which have to be cloned. */
536 llMedias.append(mtc);
537 }
538 /* Add the save state files of this machine if there is one. */
539 rc = addSaveState(machine, uCount, uTotalWeight);
540 if (FAILED(rc)) return rc;
541 }
542 /* Build up the index list of the image chain. Unfortunately we can't do
543 * that in the previous loop, cause there we go from child -> parent and
544 * didn't know how many are between. */
545 for (size_t i = 0; i < llMedias.size(); ++i)
546 {
547 uint32_t uIdx = 0;
548 MEDIUMTASKCHAIN &mtc = llMedias.at(i);
549 for (size_t a = mtc.chain.size(); a > 0; --a)
550 mtc.chain[a - 1].uIdx = uIdx++;
551 }
552
553 return rc;
554}
555
556bool MachineCloneVMPrivate::findSnapshot(const settings::SnapshotsList &snl, const Guid &id, settings::Snapshot &sn) const
557{
558 settings::SnapshotsList::const_iterator it;
559 for (it = snl.begin(); it != snl.end(); ++it)
560 {
561 if (it->uuid == id)
562 {
563 sn = (*it);
564 return true;
565 }
566 else if (!it->llChildSnapshots.empty())
567 {
568 if (findSnapshot(it->llChildSnapshots, id, sn))
569 return true;
570 }
571 }
572 return false;
573}
574
575void MachineCloneVMPrivate::updateMACAddresses(settings::NetworkAdaptersList &nwl) const
576{
577 const bool fNotNAT = options.contains(CloneOptions_KeepNATMACs);
578 settings::NetworkAdaptersList::iterator it;
579 for (it = nwl.begin(); it != nwl.end(); ++it)
580 {
581 if ( fNotNAT
582 && it->mode == NetworkAttachmentType_NAT)
583 continue;
584 Host::generateMACAddress(it->strMACAddress);
585 }
586}
587
588void MachineCloneVMPrivate::updateMACAddresses(settings::SnapshotsList &sl) const
589{
590 settings::SnapshotsList::iterator it;
591 for (it = sl.begin(); it != sl.end(); ++it)
592 {
593 updateMACAddresses(it->hardware.llNetworkAdapters);
594 if (!it->llChildSnapshots.empty())
595 updateMACAddresses(it->llChildSnapshots);
596 }
597}
598
599void MachineCloneVMPrivate::updateStorageLists(settings::StorageControllersList &sc, const Bstr &bstrOldId, const Bstr &bstrNewId) const
600{
601 settings::StorageControllersList::iterator it3;
602 for (it3 = sc.begin();
603 it3 != sc.end();
604 ++it3)
605 {
606 settings::AttachedDevicesList &llAttachments = it3->llAttachedDevices;
607 settings::AttachedDevicesList::iterator it4;
608 for (it4 = llAttachments.begin();
609 it4 != llAttachments.end();
610 ++it4)
611 {
612 if ( it4->deviceType == DeviceType_HardDisk
613 && it4->uuid == bstrOldId)
614 {
615 it4->uuid = bstrNewId;
616 }
617 }
618 }
619}
620
621void MachineCloneVMPrivate::updateSnapshotStorageLists(settings::SnapshotsList &sl, const Bstr &bstrOldId, const Bstr &bstrNewId) const
622{
623 settings::SnapshotsList::iterator it;
624 for ( it = sl.begin();
625 it != sl.end();
626 ++it)
627 {
628 updateStorageLists(it->storage.llStorageControllers, bstrOldId, bstrNewId);
629 if (!it->llChildSnapshots.empty())
630 updateSnapshotStorageLists(it->llChildSnapshots, bstrOldId, bstrNewId);
631 }
632}
633
634void MachineCloneVMPrivate::updateStateFile(settings::SnapshotsList &snl, const Guid &id, const Utf8Str &strFile) const
635{
636 settings::SnapshotsList::iterator it;
637 for (it = snl.begin(); it != snl.end(); ++it)
638 {
639 if (it->uuid == id)
640 it->strStateFile = strFile;
641 else if (!it->llChildSnapshots.empty())
642 updateStateFile(it->llChildSnapshots, id, strFile);
643 }
644}
645
646HRESULT MachineCloneVMPrivate::createDifferencingMedium(const ComObjPtr<Machine> &pMachine, const ComObjPtr<Medium> &pParent, const Utf8Str &strSnapshotFolder, RTCList<ComObjPtr<Medium> > &newMedia, ComObjPtr<Medium> *ppDiff) const
647{
648 HRESULT rc = S_OK;
649 try
650 {
651 // check validity of parent object
652 {
653 AutoReadLock alock(pParent COMMA_LOCKVAL_SRC_POS);
654 Bstr bstrSrcId;
655 rc = pParent->COMGETTER(Id)(bstrSrcId.asOutParam());
656 if (FAILED(rc)) throw rc;
657 }
658 ComObjPtr<Medium> diff;
659 diff.createObject();
660 rc = diff->init(p->getVirtualBox(),
661 pParent->getPreferredDiffFormat(),
662 Utf8StrFmt("%s%c", strSnapshotFolder.c_str(), RTPATH_DELIMITER),
663 Guid::Empty /* empty media registry */);
664 if (FAILED(rc)) throw rc;
665
666 MediumLockList *pMediumLockList(new MediumLockList());
667 rc = diff->createMediumLockList(true /* fFailIfInaccessible */,
668 true /* fMediumLockWrite */,
669 pParent,
670 *pMediumLockList);
671 if (FAILED(rc)) throw rc;
672 rc = pMediumLockList->Lock();
673 if (FAILED(rc)) throw rc;
674
675 /* this already registers the new diff image */
676 rc = pParent->createDiffStorage(diff, MediumVariant_Standard,
677 pMediumLockList,
678 NULL /* aProgress */,
679 true /* aWait */);
680 delete pMediumLockList;
681 if (FAILED(rc)) throw rc;
682 /* Remember created medium. */
683 newMedia.append(diff);
684 *ppDiff = diff;
685 }
686 catch (HRESULT rc2)
687 {
688 rc = rc2;
689 }
690 catch (...)
691 {
692 rc = VirtualBoxBase::handleUnexpectedExceptions(pMachine, RT_SRC_POS);
693 }
694
695 return rc;
696}
697
698/* static */
699int MachineCloneVMPrivate::copyStateFileProgress(unsigned uPercentage, void *pvUser)
700{
701 ComObjPtr<Progress> pProgress = *static_cast< ComObjPtr<Progress>* >(pvUser);
702
703 BOOL fCanceled = false;
704 HRESULT rc = pProgress->COMGETTER(Canceled)(&fCanceled);
705 if (FAILED(rc)) return VERR_GENERAL_FAILURE;
706 /* If canceled by the user tell it to the copy operation. */
707 if (fCanceled) return VERR_CANCELLED;
708 /* Set the new process. */
709 rc = pProgress->SetCurrentOperationProgress(uPercentage);
710 if (FAILED(rc)) return VERR_GENERAL_FAILURE;
711
712 return VINF_SUCCESS;
713}
714
715// The public class
716/////////////////////////////////////////////////////////////////////////////
717
718MachineCloneVM::MachineCloneVM(ComObjPtr<Machine> pSrcMachine, ComObjPtr<Machine> pTrgMachine, CloneMode_T mode, const RTCList<CloneOptions_T> &opts)
719 : d_ptr(new MachineCloneVMPrivate(this, pSrcMachine, pTrgMachine, mode, opts))
720{
721}
722
723MachineCloneVM::~MachineCloneVM()
724{
725 delete d_ptr;
726}
727
728HRESULT MachineCloneVM::start(IProgress **pProgress)
729{
730 DPTR(MachineCloneVM);
731 ComObjPtr<Machine> &p = d->p;
732
733 HRESULT rc;
734 try
735 {
736 /** @todo r=klaus this code cannot deal with someone crazy specifying
737 * IMachine corresponding to a mutable machine as d->pSrcMachine */
738 if (d->pSrcMachine->isSessionMachine())
739 throw p->setError(E_INVALIDARG, "The source machine is mutable");
740
741 /* Handle the special case that someone is requesting a _full_ clone
742 * with all snapshots (and the current state), but uses a snapshot
743 * machine (and not the current one) as source machine. In this case we
744 * just replace the source (snapshot) machine with the current machine. */
745 if ( d->mode == CloneMode_AllStates
746 && d->pSrcMachine->isSnapshotMachine())
747 {
748 Bstr bstrSrcMachineId;
749 rc = d->pSrcMachine->COMGETTER(Id)(bstrSrcMachineId.asOutParam());
750 if (FAILED(rc)) throw rc;
751 ComPtr<IMachine> newSrcMachine;
752 rc = d->pSrcMachine->getVirtualBox()->FindMachine(bstrSrcMachineId.raw(), newSrcMachine.asOutParam());
753 if (FAILED(rc)) throw rc;
754 d->pSrcMachine = (Machine*)(IMachine*)newSrcMachine;
755 }
756 bool fSubtreeIncludesCurrent = false;
757 ComObjPtr<Machine> pCurrState;
758 if (d->mode == CloneMode_MachineAndChildStates)
759 {
760 if (d->pSrcMachine->isSnapshotMachine())
761 {
762 /* find machine object for current snapshot of current state */
763 Bstr bstrSrcMachineId;
764 rc = d->pSrcMachine->COMGETTER(Id)(bstrSrcMachineId.asOutParam());
765 if (FAILED(rc)) throw rc;
766 ComPtr<IMachine> pCurr;
767 rc = d->pSrcMachine->getVirtualBox()->FindMachine(bstrSrcMachineId.raw(), pCurr.asOutParam());
768 if (FAILED(rc)) throw rc;
769 if (pCurr.isNull())
770 throw p->setError(VBOX_E_OBJECT_NOT_FOUND);
771 pCurrState = (Machine *)(IMachine *)pCurr;
772 ComPtr<ISnapshot> pSnapshot;
773 rc = pCurrState->COMGETTER(CurrentSnapshot)(pSnapshot.asOutParam());
774 if (FAILED(rc)) throw rc;
775 if (pSnapshot.isNull())
776 throw p->setError(VBOX_E_OBJECT_NOT_FOUND);
777 ComPtr<IMachine> pCurrSnapMachine;
778 rc = pSnapshot->COMGETTER(Machine)(pCurrSnapMachine.asOutParam());
779 if (FAILED(rc)) throw rc;
780 if (pCurrSnapMachine.isNull())
781 throw p->setError(VBOX_E_OBJECT_NOT_FOUND);
782
783 /* now check if there is a parent chain which leads to the
784 * snapshot machine defining the subtree. */
785 while (!pSnapshot.isNull())
786 {
787 ComPtr<IMachine> pSnapMachine;
788 rc = pSnapshot->COMGETTER(Machine)(pSnapMachine.asOutParam());
789 if (FAILED(rc)) throw rc;
790 if (pSnapMachine.isNull())
791 throw p->setError(VBOX_E_OBJECT_NOT_FOUND);
792 if (pSnapMachine == d->pSrcMachine)
793 {
794 fSubtreeIncludesCurrent = true;
795 break;
796 }
797 rc = pSnapshot->COMGETTER(Parent)(pSnapshot.asOutParam());
798 if (FAILED(rc)) throw rc;
799 }
800 }
801 else
802 {
803 /* If the subtree is only the Current State simply use the
804 * 'machine' case for cloning. It is easier to understand. */
805 d->mode = CloneMode_MachineState;
806 }
807 }
808
809 /* Lock the target machine early (so nobody mess around with it in the meantime). */
810 AutoWriteLock trgLock(d->pTrgMachine COMMA_LOCKVAL_SRC_POS);
811
812 if (d->pSrcMachine->isSnapshotMachine())
813 d->snapshotId = d->pSrcMachine->getSnapshotId();
814
815 /* Add the current machine and all snapshot machines below this machine
816 * in a list for further processing. */
817 RTCList< ComObjPtr<Machine> > machineList;
818
819 /* Include current state? */
820 if ( d->mode == CloneMode_MachineState
821 || d->mode == CloneMode_AllStates)
822 machineList.append(d->pSrcMachine);
823 /* Should be done a depth copy with all child snapshots? */
824 if ( d->mode == CloneMode_MachineAndChildStates
825 || d->mode == CloneMode_AllStates)
826 {
827 ULONG cSnapshots = 0;
828 rc = d->pSrcMachine->COMGETTER(SnapshotCount)(&cSnapshots);
829 if (FAILED(rc)) throw rc;
830 if (cSnapshots > 0)
831 {
832 Utf8Str id;
833 if (d->mode == CloneMode_MachineAndChildStates)
834 id = d->snapshotId.toString();
835 ComPtr<ISnapshot> pSnapshot;
836 rc = d->pSrcMachine->FindSnapshot(Bstr(id).raw(), pSnapshot.asOutParam());
837 if (FAILED(rc)) throw rc;
838 rc = d->createMachineList(pSnapshot, machineList);
839 if (FAILED(rc)) throw rc;
840 if (d->mode == CloneMode_MachineAndChildStates)
841 {
842 if (fSubtreeIncludesCurrent)
843 {
844 if (pCurrState.isNull())
845 throw p->setError(VBOX_E_OBJECT_NOT_FOUND);
846 machineList.append(pCurrState);
847 }
848 else
849 {
850 rc = pSnapshot->COMGETTER(Machine)(d->pOldMachineState.asOutParam());
851 if (FAILED(rc)) throw rc;
852 }
853 }
854 }
855 }
856
857 /* We have different approaches for getting the medias which needs to
858 * be replicated based on the clone mode the user requested (this is
859 * mostly about the full clone mode).
860 * MachineState:
861 * - Only the images which are directly attached to an source VM will
862 * be cloned. Any parent disks in the original chain will be merged
863 * into the final cloned disk.
864 * MachineAndChildStates:
865 * - In this case we search for images which have more than one
866 * children in the cloned VM or are directly attached to the new VM.
867 * All others will be merged into the remaining images which are
868 * cloned.
869 * This case is the most complicated one and needs several iterations
870 * to make sure we are only cloning images which are really
871 * necessary.
872 * AllStates:
873 * - All disks which are directly or indirectly attached to the
874 * original VM are cloned.
875 *
876 * Note: If you change something generic in one of the methods its
877 * likely that it need to be changed in the others as well! */
878 ULONG uCount = 2; /* One init task and the machine creation. */
879 ULONG uTotalWeight = 2; /* The init task and the machine creation is worth one. */
880 bool fAttachLinked = d->options.contains(CloneOptions_Link); /* Linked clones requested? */
881 switch (d->mode)
882 {
883 case CloneMode_MachineState: d->queryMediasForMachineState(machineList, fAttachLinked, uCount, uTotalWeight); break;
884 case CloneMode_MachineAndChildStates: d->queryMediasForMachineAndChildStates(machineList, fAttachLinked, uCount, uTotalWeight); break;
885 case CloneMode_AllStates: d->queryMediasForAllStates(machineList, fAttachLinked, uCount, uTotalWeight); break;
886 }
887
888 /* Now create the progress project, so the user knows whats going on. */
889 rc = d->pProgress.createObject();
890 if (FAILED(rc)) throw rc;
891 rc = d->pProgress->init(p->getVirtualBox(),
892 static_cast<IMachine*>(d->pSrcMachine) /* aInitiator */,
893 Bstr(p->tr("Cloning Machine")).raw(),
894 true /* fCancellable */,
895 uCount,
896 uTotalWeight,
897 Bstr(p->tr("Initialize Cloning")).raw(),
898 1);
899 if (FAILED(rc)) throw rc;
900
901 int vrc = d->startWorker();
902
903 if (RT_FAILURE(vrc))
904 p->setError(VBOX_E_IPRT_ERROR, "Could not create machine clone thread (%Rrc)", vrc);
905 }
906 catch (HRESULT rc2)
907 {
908 rc = rc2;
909 }
910
911 if (SUCCEEDED(rc))
912 d->pProgress.queryInterfaceTo(pProgress);
913
914 return rc;
915}
916
917HRESULT MachineCloneVM::run()
918{
919 DPTR(MachineCloneVM);
920 ComObjPtr<Machine> &p = d->p;
921
922 AutoCaller autoCaller(p);
923 if (FAILED(autoCaller.rc())) return autoCaller.rc();
924
925 AutoReadLock srcLock(p COMMA_LOCKVAL_SRC_POS);
926 AutoWriteLock trgLock(d->pTrgMachine COMMA_LOCKVAL_SRC_POS);
927
928 HRESULT rc = S_OK;
929
930 /*
931 * Todo:
932 * - What about log files?
933 */
934
935 /* Where should all the media go? */
936 Utf8Str strTrgSnapshotFolder;
937 Utf8Str strTrgMachineFolder = d->pTrgMachine->getSettingsFileFull();
938 strTrgMachineFolder.stripFilename();
939
940 RTCList<ComObjPtr<Medium> > newMedia; /* All created images */
941 RTCList<Utf8Str> newFiles; /* All extra created files (save states, ...) */
942 try
943 {
944 /* Copy all the configuration from this machine to an empty
945 * configuration dataset. */
946 settings::MachineConfigFile trgMCF = *d->pSrcMachine->mData->pMachineConfigFile;
947
948 /* Reset media registry. */
949 trgMCF.mediaRegistry.llHardDisks.clear();
950 /* If we got a valid snapshot id, replace the hardware/storage section
951 * with the stuff from the snapshot. */
952 settings::Snapshot sn;
953
954 if (d->snapshotId.isValid() && !d->snapshotId.isZero())
955 if (!d->findSnapshot(trgMCF.llFirstSnapshot, d->snapshotId, sn))
956 throw p->setError(E_FAIL,
957 p->tr("Could not find data to snapshots '%s'"), d->snapshotId.toString().c_str());
958
959
960
961 if (d->mode == CloneMode_MachineState)
962 {
963 if (sn.uuid.isValid() && !sn.uuid.isZero())
964 {
965 trgMCF.hardwareMachine = sn.hardware;
966 trgMCF.storageMachine = sn.storage;
967 }
968
969 /* Remove any hint on snapshots. */
970 trgMCF.llFirstSnapshot.clear();
971 trgMCF.uuidCurrentSnapshot.clear();
972 }
973 else if ( d->mode == CloneMode_MachineAndChildStates
974 && sn.uuid.isValid()
975 && !sn.uuid.isZero())
976 {
977 if (!d->pOldMachineState.isNull())
978 {
979 /* Copy the snapshot data to the current machine. */
980 trgMCF.hardwareMachine = sn.hardware;
981 trgMCF.storageMachine = sn.storage;
982
983 /* Current state is under root snapshot. */
984 trgMCF.uuidCurrentSnapshot = sn.uuid;
985 /* There will be created a new differencing image based on this
986 * snapshot. So reset the modified state. */
987 trgMCF.fCurrentStateModified = false;
988 }
989 /* The snapshot will be the root one. */
990 trgMCF.llFirstSnapshot.clear();
991 trgMCF.llFirstSnapshot.push_back(sn);
992 }
993
994 /* Generate new MAC addresses for all machines when not forbidden. */
995 if (!d->options.contains(CloneOptions_KeepAllMACs))
996 {
997 d->updateMACAddresses(trgMCF.hardwareMachine.llNetworkAdapters);
998 d->updateMACAddresses(trgMCF.llFirstSnapshot);
999 }
1000
1001 /* When the current snapshot folder is absolute we reset it to the
1002 * default relative folder. */
1003 if (RTPathStartsWithRoot(trgMCF.machineUserData.strSnapshotFolder.c_str()))
1004 trgMCF.machineUserData.strSnapshotFolder = "Snapshots";
1005 trgMCF.strStateFile = "";
1006 /* Set the new name. */
1007 const Utf8Str strOldVMName = trgMCF.machineUserData.strName;
1008 trgMCF.machineUserData.strName = d->pTrgMachine->mUserData->s.strName;
1009 trgMCF.uuid = d->pTrgMachine->mData->mUuid;
1010
1011 Bstr bstrSrcSnapshotFolder;
1012 rc = d->pSrcMachine->COMGETTER(SnapshotFolder)(bstrSrcSnapshotFolder.asOutParam());
1013 if (FAILED(rc)) throw rc;
1014 /* The absolute name of the snapshot folder. */
1015 strTrgSnapshotFolder = Utf8StrFmt("%s%c%s", strTrgMachineFolder.c_str(), RTPATH_DELIMITER, trgMCF.machineUserData.strSnapshotFolder.c_str());
1016
1017 /* Should we rename the disk names. */
1018 bool fKeepDiskNames = d->options.contains(CloneOptions_KeepDiskNames);
1019
1020 /* We need to create a map with the already created medias. This is
1021 * necessary, cause different snapshots could have the same
1022 * parents/parent chain. If a medium is in this map already, it isn't
1023 * cloned a second time, but simply used. */
1024 typedef std::map<Utf8Str, ComObjPtr<Medium> > TStrMediumMap;
1025 typedef std::pair<Utf8Str, ComObjPtr<Medium> > TStrMediumPair;
1026 TStrMediumMap map;
1027 size_t cDisks = 0;
1028 for (size_t i = 0; i < d->llMedias.size(); ++i)
1029 {
1030 const MEDIUMTASKCHAIN &mtc = d->llMedias.at(i);
1031 ComObjPtr<Medium> pNewParent;
1032 uint32_t uSrcParentIdx = UINT32_MAX;
1033 uint32_t uTrgParentIdx = UINT32_MAX;
1034 for (size_t a = mtc.chain.size(); a > 0; --a)
1035 {
1036 const MEDIUMTASK &mt = mtc.chain.at(a - 1);
1037 ComPtr<IMedium> pMedium = mt.pMedium;
1038
1039 Bstr bstrSrcName;
1040 rc = pMedium->COMGETTER(Name)(bstrSrcName.asOutParam());
1041 if (FAILED(rc)) throw rc;
1042
1043 rc = d->pProgress->SetNextOperation(BstrFmt(p->tr("Cloning Disk '%ls' ..."), bstrSrcName.raw()).raw(), mt.uWeight);
1044 if (FAILED(rc)) throw rc;
1045
1046 Bstr bstrSrcId;
1047 rc = pMedium->COMGETTER(Id)(bstrSrcId.asOutParam());
1048 if (FAILED(rc)) throw rc;
1049
1050 if (mtc.fAttachLinked)
1051 {
1052 IMedium *pTmp = pMedium;
1053 ComObjPtr<Medium> pLMedium = static_cast<Medium*>(pTmp);
1054 if (pLMedium.isNull())
1055 throw p->setError(VBOX_E_OBJECT_NOT_FOUND);
1056 ComObjPtr<Medium> pBase = pLMedium->getBase();
1057 if (pBase->isReadOnly())
1058 {
1059 ComObjPtr<Medium> pDiff;
1060 /* create the diff under the snapshot medium */
1061 trgLock.release();
1062 srcLock.release();
1063 rc = d->createDifferencingMedium(p, pLMedium, strTrgSnapshotFolder,
1064 newMedia, &pDiff);
1065 srcLock.acquire();
1066 trgLock.acquire();
1067 if (FAILED(rc)) throw rc;
1068 map.insert(TStrMediumPair(Utf8Str(bstrSrcId), pDiff));
1069 /* diff image has to be used... */
1070 pNewParent = pDiff;
1071 }
1072 else
1073 {
1074 /* Attach the medium directly, as its type is not
1075 * subject to diff creation. */
1076 newMedia.append(pLMedium);
1077 map.insert(TStrMediumPair(Utf8Str(bstrSrcId), pLMedium));
1078 pNewParent = pLMedium;
1079 }
1080 }
1081 else
1082 {
1083 /* Is a clone already there? */
1084 TStrMediumMap::iterator it = map.find(Utf8Str(bstrSrcId));
1085 if (it != map.end())
1086 pNewParent = it->second;
1087 else
1088 {
1089 ComPtr<IMediumFormat> pSrcFormat;
1090 rc = pMedium->COMGETTER(MediumFormat)(pSrcFormat.asOutParam());
1091 ULONG uSrcCaps = 0;
1092 com::SafeArray <MediumFormatCapabilities_T> mediumFormatCap;
1093 rc = pSrcFormat->COMGETTER(Capabilities)(ComSafeArrayAsOutParam(mediumFormatCap));
1094
1095 if (FAILED(rc)) throw rc;
1096 else
1097 {
1098 for (ULONG j = 0; j < mediumFormatCap.size(); j++)
1099 uSrcCaps |= mediumFormatCap[j];
1100 }
1101
1102 /* Default format? */
1103 Utf8Str strDefaultFormat;
1104 p->mParent->getDefaultHardDiskFormat(strDefaultFormat);
1105 Bstr bstrSrcFormat(strDefaultFormat);
1106
1107 ULONG srcVar = MediumVariant_Standard;
1108 com::SafeArray <MediumVariant_T> mediumVariant;
1109
1110 /* Is the source file based? */
1111 if ((uSrcCaps & MediumFormatCapabilities_File) == MediumFormatCapabilities_File)
1112 {
1113 /* Yes, just use the source format. Otherwise the defaults
1114 * will be used. */
1115 rc = pMedium->COMGETTER(Format)(bstrSrcFormat.asOutParam());
1116 if (FAILED(rc)) throw rc;
1117
1118 rc = pMedium->COMGETTER(Variant)(ComSafeArrayAsOutParam(mediumVariant));
1119 if (FAILED(rc)) throw rc;
1120 else
1121 {
1122 for (size_t j = 0; j < mediumVariant.size(); j++)
1123 srcVar |= mediumVariant[j];
1124 }
1125 }
1126
1127 Guid newId;
1128 newId.create();
1129 Utf8Str strNewName(bstrSrcName);
1130 if (!fKeepDiskNames)
1131 {
1132 Utf8Str strSrcTest = bstrSrcName;
1133 /* Check if we have to use another name. */
1134 if (!mt.strBaseName.isEmpty())
1135 strSrcTest = mt.strBaseName;
1136 strSrcTest.stripExt();
1137 /* If the old disk name was in {uuid} format we also
1138 * want the new name in this format, but with the
1139 * updated id of course. If the old disk was called
1140 * like the VM name, we change it to the new VM name.
1141 * For all other disks we rename them with this
1142 * template: "new name-disk1.vdi". */
1143 if (strSrcTest == strOldVMName)
1144 strNewName = Utf8StrFmt("%s%s", trgMCF.machineUserData.strName.c_str(), RTPathExt(Utf8Str(bstrSrcName).c_str()));
1145 else if ( strSrcTest.startsWith("{")
1146 && strSrcTest.endsWith("}"))
1147 {
1148 strSrcTest = strSrcTest.substr(1, strSrcTest.length() - 2);
1149
1150 Guid temp_guid(strSrcTest);
1151 if (temp_guid.isValid() && !temp_guid.isZero())
1152 strNewName = Utf8StrFmt("%s%s", newId.toStringCurly().c_str(), RTPathExt(strNewName.c_str()));
1153 }
1154 else
1155 strNewName = Utf8StrFmt("%s-disk%d%s", trgMCF.machineUserData.strName.c_str(), ++cDisks, RTPathExt(Utf8Str(bstrSrcName).c_str()));
1156 }
1157
1158 /* Check if this medium comes from the snapshot folder, if
1159 * so, put it there in the cloned machine as well.
1160 * Otherwise it goes to the machine folder. */
1161 Bstr bstrSrcPath;
1162 Utf8Str strFile = Utf8StrFmt("%s%c%s", strTrgMachineFolder.c_str(), RTPATH_DELIMITER, strNewName.c_str());
1163 rc = pMedium->COMGETTER(Location)(bstrSrcPath.asOutParam());
1164 if (FAILED(rc)) throw rc;
1165 if ( !bstrSrcPath.isEmpty()
1166 && RTPathStartsWith(Utf8Str(bstrSrcPath).c_str(), Utf8Str(bstrSrcSnapshotFolder).c_str())
1167 && (fKeepDiskNames || mt.strBaseName.isEmpty()))
1168 strFile = Utf8StrFmt("%s%c%s", strTrgSnapshotFolder.c_str(), RTPATH_DELIMITER, strNewName.c_str());
1169
1170 /* Start creating the clone. */
1171 ComObjPtr<Medium> pTarget;
1172 rc = pTarget.createObject();
1173 if (FAILED(rc)) throw rc;
1174
1175 rc = pTarget->init(p->mParent,
1176 Utf8Str(bstrSrcFormat),
1177 strFile,
1178 Guid::Empty /* empty media registry */);
1179 if (FAILED(rc)) throw rc;
1180
1181 /* Update the new uuid. */
1182 pTarget->updateId(newId);
1183
1184 srcLock.release();
1185 /* Do the disk cloning. */
1186 ComPtr<IProgress> progress2;
1187
1188 ComObjPtr<Medium> pLMedium = static_cast<Medium*>((IMedium*)pMedium);
1189 rc = pLMedium->cloneToEx(pTarget,
1190 srcVar,
1191 pNewParent,
1192 progress2.asOutParam(),
1193 uSrcParentIdx,
1194 uTrgParentIdx);
1195 if (FAILED(rc)) throw rc;
1196
1197 /* Wait until the async process has finished. */
1198 rc = d->pProgress->WaitForAsyncProgressCompletion(progress2);
1199 srcLock.acquire();
1200 if (FAILED(rc)) throw rc;
1201
1202 /* Check the result of the async process. */
1203 LONG iRc;
1204 rc = progress2->COMGETTER(ResultCode)(&iRc);
1205 if (FAILED(rc)) throw rc;
1206 /* If the thread of the progress object has an error, then
1207 * retrieve the error info from there, or it'll be lost. */
1208 if (FAILED(iRc))
1209 throw p->setError(ProgressErrorInfo(progress2));
1210 /* Remember created medium. */
1211 newMedia.append(pTarget);
1212 /* Get the medium type from the source and set it to the
1213 * new medium. */
1214 MediumType_T type;
1215 rc = pMedium->COMGETTER(Type)(&type);
1216 if (FAILED(rc)) throw rc;
1217 rc = pTarget->COMSETTER(Type)(type);
1218 if (FAILED(rc)) throw rc;
1219 map.insert(TStrMediumPair(Utf8Str(bstrSrcId), pTarget));
1220 /* register the new harddisk */
1221 {
1222 AutoWriteLock tlock(p->mParent->getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
1223 rc = p->mParent->registerMedium(pTarget, &pTarget,
1224 DeviceType_HardDisk);
1225 if (FAILED(rc)) throw rc;
1226 }
1227 /* This medium becomes the parent of the next medium in the
1228 * chain. */
1229 pNewParent = pTarget;
1230 }
1231 }
1232 /* Save the current source medium index as the new parent
1233 * medium index. */
1234 uSrcParentIdx = mt.uIdx;
1235 /* Simply increase the target index. */
1236 ++uTrgParentIdx;
1237 }
1238
1239 Bstr bstrSrcId;
1240 rc = mtc.chain.first().pMedium->COMGETTER(Id)(bstrSrcId.asOutParam());
1241 if (FAILED(rc)) throw rc;
1242 Bstr bstrTrgId;
1243 rc = pNewParent->COMGETTER(Id)(bstrTrgId.asOutParam());
1244 if (FAILED(rc)) throw rc;
1245 /* update snapshot configuration */
1246 d->updateSnapshotStorageLists(trgMCF.llFirstSnapshot, bstrSrcId, bstrTrgId);
1247
1248 /* create new 'Current State' diff for caller defined place */
1249 if (mtc.fCreateDiffs)
1250 {
1251 const MEDIUMTASK &mt = mtc.chain.first();
1252 ComObjPtr<Medium> pLMedium = static_cast<Medium*>((IMedium*)mt.pMedium);
1253 if (pLMedium.isNull())
1254 throw p->setError(VBOX_E_OBJECT_NOT_FOUND);
1255 ComObjPtr<Medium> pBase = pLMedium->getBase();
1256 if (pBase->isReadOnly())
1257 {
1258 ComObjPtr<Medium> pDiff;
1259 trgLock.release();
1260 srcLock.release();
1261 rc = d->createDifferencingMedium(p, pNewParent, strTrgSnapshotFolder,
1262 newMedia, &pDiff);
1263 srcLock.acquire();
1264 trgLock.acquire();
1265 if (FAILED(rc)) throw rc;
1266 /* diff image has to be used... */
1267 pNewParent = pDiff;
1268 }
1269 else
1270 {
1271 /* Attach the medium directly, as its type is not
1272 * subject to diff creation. */
1273 newMedia.append(pNewParent);
1274 }
1275
1276 rc = pNewParent->COMGETTER(Id)(bstrTrgId.asOutParam());
1277 if (FAILED(rc)) throw rc;
1278 }
1279 /* update 'Current State' configuration */
1280 d->updateStorageLists(trgMCF.storageMachine.llStorageControllers, bstrSrcId, bstrTrgId);
1281 }
1282 /* Make sure all disks know of the new machine uuid. We do this last to
1283 * be able to change the medium type above. */
1284 for (size_t i = newMedia.size(); i > 0; --i)
1285 {
1286 const ComObjPtr<Medium> &pMedium = newMedia.at(i - 1);
1287 AutoCaller mac(pMedium);
1288 if (FAILED(mac.rc())) throw mac.rc();
1289 AutoWriteLock mlock(pMedium COMMA_LOCKVAL_SRC_POS);
1290 Guid uuid = d->pTrgMachine->mData->mUuid;
1291 if (d->options.contains(CloneOptions_Link))
1292 {
1293 ComObjPtr<Medium> pParent = pMedium->getParent();
1294 mlock.release();
1295 if (!pParent.isNull())
1296 {
1297 AutoCaller mac2(pParent);
1298 if (FAILED(mac2.rc())) throw mac2.rc();
1299 AutoReadLock mlock2(pParent COMMA_LOCKVAL_SRC_POS);
1300 if (pParent->getFirstRegistryMachineId(uuid))
1301 {
1302 mlock2.release();
1303 trgLock.release();
1304 srcLock.release();
1305 p->mParent->markRegistryModified(uuid);
1306 srcLock.acquire();
1307 trgLock.acquire();
1308 mlock2.acquire();
1309 }
1310 }
1311 mlock.acquire();
1312 }
1313 pMedium->addRegistry(uuid, false /* fRecurse */);
1314 }
1315 /* Check if a snapshot folder is necessary and if so doesn't already
1316 * exists. */
1317 if ( !d->llSaveStateFiles.isEmpty()
1318 && !RTDirExists(strTrgSnapshotFolder.c_str()))
1319 {
1320 int vrc = RTDirCreateFullPath(strTrgSnapshotFolder.c_str(), 0700);
1321 if (RT_FAILURE(vrc))
1322 throw p->setError(VBOX_E_IPRT_ERROR,
1323 p->tr("Could not create snapshots folder '%s' (%Rrc)"), strTrgSnapshotFolder.c_str(), vrc);
1324 }
1325 /* Clone all save state files. */
1326 for (size_t i = 0; i < d->llSaveStateFiles.size(); ++i)
1327 {
1328 SAVESTATETASK sst = d->llSaveStateFiles.at(i);
1329 const Utf8Str &strTrgSaveState = Utf8StrFmt("%s%c%s", strTrgSnapshotFolder.c_str(), RTPATH_DELIMITER, RTPathFilename(sst.strSaveStateFile.c_str()));
1330
1331 /* Move to next sub-operation. */
1332 rc = d->pProgress->SetNextOperation(BstrFmt(p->tr("Copy save state file '%s' ..."), RTPathFilename(sst.strSaveStateFile.c_str())).raw(), sst.uWeight);
1333 if (FAILED(rc)) throw rc;
1334 /* Copy the file only if it was not copied already. */
1335 if (!newFiles.contains(strTrgSaveState.c_str()))
1336 {
1337 int vrc = RTFileCopyEx(sst.strSaveStateFile.c_str(), strTrgSaveState.c_str(), 0, MachineCloneVMPrivate::copyStateFileProgress, &d->pProgress);
1338 if (RT_FAILURE(vrc))
1339 throw p->setError(VBOX_E_IPRT_ERROR,
1340 p->tr("Could not copy state file '%s' to '%s' (%Rrc)"), sst.strSaveStateFile.c_str(), strTrgSaveState.c_str(), vrc);
1341 newFiles.append(strTrgSaveState);
1342 }
1343 /* Update the path in the configuration either for the current
1344 * machine state or the snapshots. */
1345 if (!sst.snapshotUuid.isValid() || sst.snapshotUuid.isZero())
1346 trgMCF.strStateFile = strTrgSaveState;
1347 else
1348 d->updateStateFile(trgMCF.llFirstSnapshot, sst.snapshotUuid, strTrgSaveState);
1349 }
1350
1351 {
1352 rc = d->pProgress->SetNextOperation(BstrFmt(p->tr("Create Machine Clone '%s' ..."), trgMCF.machineUserData.strName.c_str()).raw(), 1);
1353 if (FAILED(rc)) throw rc;
1354 /* After modifying the new machine config, we can copy the stuff
1355 * over to the new machine. The machine have to be mutable for
1356 * this. */
1357 rc = d->pTrgMachine->checkStateDependency(p->MutableStateDep);
1358 if (FAILED(rc)) throw rc;
1359 rc = d->pTrgMachine->loadMachineDataFromSettings(trgMCF,
1360 &d->pTrgMachine->mData->mUuid);
1361 if (FAILED(rc)) throw rc;
1362 /* save all VM data */
1363 bool fNeedsGlobalSaveSettings = false;
1364 rc = d->pTrgMachine->saveSettings(&fNeedsGlobalSaveSettings, Machine::SaveS_Force);
1365 if (FAILED(rc)) throw rc;
1366 /* Release all locks */
1367 trgLock.release();
1368 srcLock.release();
1369 if (fNeedsGlobalSaveSettings)
1370 {
1371 /* save the global settings; for that we should hold only the
1372 * VirtualBox lock */
1373 AutoWriteLock vlock(p->mParent COMMA_LOCKVAL_SRC_POS);
1374 rc = p->mParent->saveSettings();
1375 if (FAILED(rc)) throw rc;
1376 }
1377 }
1378
1379 /* Any additional machines need saving? */
1380 p->mParent->saveModifiedRegistries();
1381 }
1382 catch (HRESULT rc2)
1383 {
1384 rc = rc2;
1385 }
1386 catch (...)
1387 {
1388 rc = VirtualBoxBase::handleUnexpectedExceptions(p, RT_SRC_POS);
1389 }
1390
1391 MultiResult mrc(rc);
1392 /* Cleanup on failure (CANCEL also) */
1393 if (FAILED(rc))
1394 {
1395 int vrc = VINF_SUCCESS;
1396 /* Delete all created files. */
1397 for (size_t i = 0; i < newFiles.size(); ++i)
1398 {
1399 vrc = RTFileDelete(newFiles.at(i).c_str());
1400 if (RT_FAILURE(vrc))
1401 mrc = p->setError(VBOX_E_IPRT_ERROR, p->tr("Could not delete file '%s' (%Rrc)"), newFiles.at(i).c_str(), vrc);
1402 }
1403 /* Delete all already created medias. (Reverse, cause there could be
1404 * parent->child relations.) */
1405 for (size_t i = newMedia.size(); i > 0; --i)
1406 {
1407 const ComObjPtr<Medium> &pMedium = newMedia.at(i - 1);
1408 mrc = pMedium->deleteStorage(NULL /* aProgress */,
1409 true /* aWait */);
1410 pMedium->Close();
1411 }
1412 /* Delete the snapshot folder when not empty. */
1413 if (!strTrgSnapshotFolder.isEmpty())
1414 RTDirRemove(strTrgSnapshotFolder.c_str());
1415 /* Delete the machine folder when not empty. */
1416 RTDirRemove(strTrgMachineFolder.c_str());
1417
1418 /* Must save the modified registries */
1419 p->mParent->saveModifiedRegistries();
1420 }
1421
1422 return mrc;
1423}
1424
1425void MachineCloneVM::destroy()
1426{
1427 delete this;
1428}
1429
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