VirtualBox

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

Last change on this file since 55457 was 54948, checked in by vboxsync, 10 years ago

Main/Medium+Snapshot: make all code recursing over trees (objects containing lists of child objects) use as little stack as possible, and establish safe depth limits to avoid crashes, plus related cleanups in related code areas

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