VirtualBox

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

Last change on this file since 52489 was 52095, checked in by vboxsync, 10 years ago

Main/Medium+AutoCaller+others: fix medium uninit deadlock caused by lock order violations, sometimes taking the caller before the media tree lock, sometimes after, plus a few other small fixes

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 61.0 KB
Line 
1/* $Id: MachineImplCloneVM.cpp 52095 2014-07-18 09:14:01Z vboxsync $ */
2/** @file
3 * Implementation of MachineCloneVM
4 */
5
6/*
7 * Copyright (C) 2011-2014 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 if (FAILED(rc)) throw rc;
681
682 MediumLockList *pMediumLockList(new MediumLockList());
683 rc = diff->i_createMediumLockList(true /* fFailIfInaccessible */,
684 true /* fMediumLockWrite */,
685 pParent,
686 *pMediumLockList);
687 if (FAILED(rc)) throw rc;
688 rc = pMediumLockList->Lock();
689 if (FAILED(rc)) throw rc;
690
691 /* this already registers the new diff image */
692 rc = pParent->i_createDiffStorage(diff, MediumVariant_Standard,
693 pMediumLockList,
694 NULL /* aProgress */,
695 true /* aWait */);
696 delete pMediumLockList;
697 if (FAILED(rc)) throw rc;
698 /* Remember created medium. */
699 newMedia.append(diff);
700 *ppDiff = diff;
701 }
702 catch (HRESULT rc2)
703 {
704 rc = rc2;
705 }
706 catch (...)
707 {
708 rc = VirtualBoxBase::handleUnexpectedExceptions(pMachine, RT_SRC_POS);
709 }
710
711 return rc;
712}
713
714/* static */
715int MachineCloneVMPrivate::copyStateFileProgress(unsigned uPercentage, void *pvUser)
716{
717 ComObjPtr<Progress> pProgress = *static_cast< ComObjPtr<Progress>* >(pvUser);
718
719 BOOL fCanceled = false;
720 HRESULT rc = pProgress->COMGETTER(Canceled)(&fCanceled);
721 if (FAILED(rc)) return VERR_GENERAL_FAILURE;
722 /* If canceled by the user tell it to the copy operation. */
723 if (fCanceled) return VERR_CANCELLED;
724 /* Set the new process. */
725 rc = pProgress->SetCurrentOperationProgress(uPercentage);
726 if (FAILED(rc)) return VERR_GENERAL_FAILURE;
727
728 return VINF_SUCCESS;
729}
730
731// The public class
732/////////////////////////////////////////////////////////////////////////////
733
734MachineCloneVM::MachineCloneVM(ComObjPtr<Machine> pSrcMachine, ComObjPtr<Machine> pTrgMachine, CloneMode_T mode,
735 const RTCList<CloneOptions_T> &opts) :
736 d_ptr(new MachineCloneVMPrivate(this, pSrcMachine, pTrgMachine, mode, opts))
737{
738}
739
740MachineCloneVM::~MachineCloneVM()
741{
742 delete d_ptr;
743}
744
745HRESULT MachineCloneVM::start(IProgress **pProgress)
746{
747 DPTR(MachineCloneVM);
748 ComObjPtr<Machine> &p = d->p;
749
750 HRESULT rc;
751 try
752 {
753 /** @todo r=klaus this code cannot deal with someone crazy specifying
754 * IMachine corresponding to a mutable machine as d->pSrcMachine */
755 if (d->pSrcMachine->i_isSessionMachine())
756 throw p->setError(E_INVALIDARG, "The source machine is mutable");
757
758 /* Handle the special case that someone is requesting a _full_ clone
759 * with all snapshots (and the current state), but uses a snapshot
760 * machine (and not the current one) as source machine. In this case we
761 * just replace the source (snapshot) machine with the current machine. */
762 if ( d->mode == CloneMode_AllStates
763 && d->pSrcMachine->i_isSnapshotMachine())
764 {
765 Bstr bstrSrcMachineId;
766 rc = d->pSrcMachine->COMGETTER(Id)(bstrSrcMachineId.asOutParam());
767 if (FAILED(rc)) throw rc;
768 ComPtr<IMachine> newSrcMachine;
769 rc = d->pSrcMachine->i_getVirtualBox()->FindMachine(bstrSrcMachineId.raw(), newSrcMachine.asOutParam());
770 if (FAILED(rc)) throw rc;
771 d->pSrcMachine = (Machine*)(IMachine*)newSrcMachine;
772 }
773 bool fSubtreeIncludesCurrent = false;
774 ComObjPtr<Machine> pCurrState;
775 if (d->mode == CloneMode_MachineAndChildStates)
776 {
777 if (d->pSrcMachine->i_isSnapshotMachine())
778 {
779 /* find machine object for current snapshot of current state */
780 Bstr bstrSrcMachineId;
781 rc = d->pSrcMachine->COMGETTER(Id)(bstrSrcMachineId.asOutParam());
782 if (FAILED(rc)) throw rc;
783 ComPtr<IMachine> pCurr;
784 rc = d->pSrcMachine->i_getVirtualBox()->FindMachine(bstrSrcMachineId.raw(), pCurr.asOutParam());
785 if (FAILED(rc)) throw rc;
786 if (pCurr.isNull())
787 throw p->setError(VBOX_E_OBJECT_NOT_FOUND);
788 pCurrState = (Machine *)(IMachine *)pCurr;
789 ComPtr<ISnapshot> pSnapshot;
790 rc = pCurrState->COMGETTER(CurrentSnapshot)(pSnapshot.asOutParam());
791 if (FAILED(rc)) throw rc;
792 if (pSnapshot.isNull())
793 throw p->setError(VBOX_E_OBJECT_NOT_FOUND);
794 ComPtr<IMachine> pCurrSnapMachine;
795 rc = pSnapshot->COMGETTER(Machine)(pCurrSnapMachine.asOutParam());
796 if (FAILED(rc)) throw rc;
797 if (pCurrSnapMachine.isNull())
798 throw p->setError(VBOX_E_OBJECT_NOT_FOUND);
799
800 /* now check if there is a parent chain which leads to the
801 * snapshot machine defining the subtree. */
802 while (!pSnapshot.isNull())
803 {
804 ComPtr<IMachine> pSnapMachine;
805 rc = pSnapshot->COMGETTER(Machine)(pSnapMachine.asOutParam());
806 if (FAILED(rc)) throw rc;
807 if (pSnapMachine.isNull())
808 throw p->setError(VBOX_E_OBJECT_NOT_FOUND);
809 if (pSnapMachine == d->pSrcMachine)
810 {
811 fSubtreeIncludesCurrent = true;
812 break;
813 }
814 rc = pSnapshot->COMGETTER(Parent)(pSnapshot.asOutParam());
815 if (FAILED(rc)) throw rc;
816 }
817 }
818 else
819 {
820 /* If the subtree is only the Current State simply use the
821 * 'machine' case for cloning. It is easier to understand. */
822 d->mode = CloneMode_MachineState;
823 }
824 }
825
826 /* Lock the target machine early (so nobody mess around with it in the meantime). */
827 AutoWriteLock trgLock(d->pTrgMachine COMMA_LOCKVAL_SRC_POS);
828
829 if (d->pSrcMachine->i_isSnapshotMachine())
830 d->snapshotId = d->pSrcMachine->i_getSnapshotId();
831
832 /* Add the current machine and all snapshot machines below this machine
833 * in a list for further processing. */
834 RTCList< ComObjPtr<Machine> > machineList;
835
836 /* Include current state? */
837 if ( d->mode == CloneMode_MachineState
838 || d->mode == CloneMode_AllStates)
839 machineList.append(d->pSrcMachine);
840 /* Should be done a depth copy with all child snapshots? */
841 if ( d->mode == CloneMode_MachineAndChildStates
842 || d->mode == CloneMode_AllStates)
843 {
844 ULONG cSnapshots = 0;
845 rc = d->pSrcMachine->COMGETTER(SnapshotCount)(&cSnapshots);
846 if (FAILED(rc)) throw rc;
847 if (cSnapshots > 0)
848 {
849 Utf8Str id;
850 if (d->mode == CloneMode_MachineAndChildStates)
851 id = d->snapshotId.toString();
852 ComPtr<ISnapshot> pSnapshot;
853 rc = d->pSrcMachine->FindSnapshot(Bstr(id).raw(), pSnapshot.asOutParam());
854 if (FAILED(rc)) throw rc;
855 rc = d->createMachineList(pSnapshot, machineList);
856 if (FAILED(rc)) throw rc;
857 if (d->mode == CloneMode_MachineAndChildStates)
858 {
859 if (fSubtreeIncludesCurrent)
860 {
861 if (pCurrState.isNull())
862 throw p->setError(VBOX_E_OBJECT_NOT_FOUND);
863 machineList.append(pCurrState);
864 }
865 else
866 {
867 rc = pSnapshot->COMGETTER(Machine)(d->pOldMachineState.asOutParam());
868 if (FAILED(rc)) throw rc;
869 }
870 }
871 }
872 }
873
874 /* We have different approaches for getting the medias which needs to
875 * be replicated based on the clone mode the user requested (this is
876 * mostly about the full clone mode).
877 * MachineState:
878 * - Only the images which are directly attached to an source VM will
879 * be cloned. Any parent disks in the original chain will be merged
880 * into the final cloned disk.
881 * MachineAndChildStates:
882 * - In this case we search for images which have more than one
883 * children in the cloned VM or are directly attached to the new VM.
884 * All others will be merged into the remaining images which are
885 * cloned.
886 * This case is the most complicated one and needs several iterations
887 * to make sure we are only cloning images which are really
888 * necessary.
889 * AllStates:
890 * - All disks which are directly or indirectly attached to the
891 * original VM are cloned.
892 *
893 * Note: If you change something generic in one of the methods its
894 * likely that it need to be changed in the others as well! */
895 ULONG uCount = 2; /* One init task and the machine creation. */
896 ULONG uTotalWeight = 2; /* The init task and the machine creation is worth one. */
897 bool fAttachLinked = d->options.contains(CloneOptions_Link); /* Linked clones requested? */
898 switch (d->mode)
899 {
900 case CloneMode_MachineState: d->queryMediasForMachineState(machineList, fAttachLinked,
901 uCount, uTotalWeight);
902 break;
903 case CloneMode_MachineAndChildStates: d->queryMediasForMachineAndChildStates(machineList, fAttachLinked,
904 uCount, uTotalWeight);
905 break;
906 case CloneMode_AllStates: d->queryMediasForAllStates(machineList, fAttachLinked, uCount,
907 uTotalWeight);
908 break;
909 }
910
911 /* Now create the progress project, so the user knows whats going on. */
912 rc = d->pProgress.createObject();
913 if (FAILED(rc)) throw rc;
914 rc = d->pProgress->init(p->i_getVirtualBox(),
915 static_cast<IMachine*>(d->pSrcMachine) /* aInitiator */,
916 Bstr(p->tr("Cloning Machine")).raw(),
917 true /* fCancellable */,
918 uCount,
919 uTotalWeight,
920 Bstr(p->tr("Initialize Cloning")).raw(),
921 1);
922 if (FAILED(rc)) throw rc;
923
924 int vrc = d->startWorker();
925
926 if (RT_FAILURE(vrc))
927 p->setError(VBOX_E_IPRT_ERROR, "Could not create machine clone thread (%Rrc)", vrc);
928 }
929 catch (HRESULT rc2)
930 {
931 rc = rc2;
932 }
933
934 if (SUCCEEDED(rc))
935 d->pProgress.queryInterfaceTo(pProgress);
936
937 return rc;
938}
939
940HRESULT MachineCloneVM::run()
941{
942 DPTR(MachineCloneVM);
943 ComObjPtr<Machine> &p = d->p;
944
945 AutoCaller autoCaller(p);
946 if (FAILED(autoCaller.rc())) return autoCaller.rc();
947
948 AutoReadLock srcLock(p COMMA_LOCKVAL_SRC_POS);
949 AutoWriteLock trgLock(d->pTrgMachine COMMA_LOCKVAL_SRC_POS);
950
951 HRESULT rc = S_OK;
952
953 /*
954 * Todo:
955 * - What about log files?
956 */
957
958 /* Where should all the media go? */
959 Utf8Str strTrgSnapshotFolder;
960 Utf8Str strTrgMachineFolder = d->pTrgMachine->i_getSettingsFileFull();
961 strTrgMachineFolder.stripFilename();
962
963 RTCList<ComObjPtr<Medium> > newMedia; /* All created images */
964 RTCList<Utf8Str> newFiles; /* All extra created files (save states, ...) */
965 try
966 {
967 /* Copy all the configuration from this machine to an empty
968 * configuration dataset. */
969 settings::MachineConfigFile trgMCF = *d->pSrcMachine->mData->pMachineConfigFile;
970
971 /* Reset media registry. */
972 trgMCF.mediaRegistry.llHardDisks.clear();
973 /* If we got a valid snapshot id, replace the hardware/storage section
974 * with the stuff from the snapshot. */
975 settings::Snapshot sn;
976
977 if (d->snapshotId.isValid() && !d->snapshotId.isZero())
978 if (!d->findSnapshot(trgMCF.llFirstSnapshot, d->snapshotId, sn))
979 throw p->setError(E_FAIL,
980 p->tr("Could not find data to snapshots '%s'"), d->snapshotId.toString().c_str());
981
982
983
984 if (d->mode == CloneMode_MachineState)
985 {
986 if (sn.uuid.isValid() && !sn.uuid.isZero())
987 {
988 trgMCF.hardwareMachine = sn.hardware;
989 trgMCF.storageMachine = sn.storage;
990 }
991
992 /* Remove any hint on snapshots. */
993 trgMCF.llFirstSnapshot.clear();
994 trgMCF.uuidCurrentSnapshot.clear();
995 }
996 else if ( d->mode == CloneMode_MachineAndChildStates
997 && sn.uuid.isValid()
998 && !sn.uuid.isZero())
999 {
1000 if (!d->pOldMachineState.isNull())
1001 {
1002 /* Copy the snapshot data to the current machine. */
1003 trgMCF.hardwareMachine = sn.hardware;
1004 trgMCF.storageMachine = sn.storage;
1005
1006 /* Current state is under root snapshot. */
1007 trgMCF.uuidCurrentSnapshot = sn.uuid;
1008 /* There will be created a new differencing image based on this
1009 * snapshot. So reset the modified state. */
1010 trgMCF.fCurrentStateModified = false;
1011 }
1012 /* The snapshot will be the root one. */
1013 trgMCF.llFirstSnapshot.clear();
1014 trgMCF.llFirstSnapshot.push_back(sn);
1015 }
1016
1017 /* Generate new MAC addresses for all machines when not forbidden. */
1018 if (!d->options.contains(CloneOptions_KeepAllMACs))
1019 {
1020 d->updateMACAddresses(trgMCF.hardwareMachine.llNetworkAdapters);
1021 d->updateMACAddresses(trgMCF.llFirstSnapshot);
1022 }
1023
1024 /* When the current snapshot folder is absolute we reset it to the
1025 * default relative folder. */
1026 if (RTPathStartsWithRoot(trgMCF.machineUserData.strSnapshotFolder.c_str()))
1027 trgMCF.machineUserData.strSnapshotFolder = "Snapshots";
1028 trgMCF.strStateFile = "";
1029 /* Set the new name. */
1030 const Utf8Str strOldVMName = trgMCF.machineUserData.strName;
1031 trgMCF.machineUserData.strName = d->pTrgMachine->mUserData->s.strName;
1032 trgMCF.uuid = d->pTrgMachine->mData->mUuid;
1033
1034 Bstr bstrSrcSnapshotFolder;
1035 rc = d->pSrcMachine->COMGETTER(SnapshotFolder)(bstrSrcSnapshotFolder.asOutParam());
1036 if (FAILED(rc)) throw rc;
1037 /* The absolute name of the snapshot folder. */
1038 strTrgSnapshotFolder = Utf8StrFmt("%s%c%s", strTrgMachineFolder.c_str(), RTPATH_DELIMITER,
1039 trgMCF.machineUserData.strSnapshotFolder.c_str());
1040
1041 /* Should we rename the disk names. */
1042 bool fKeepDiskNames = d->options.contains(CloneOptions_KeepDiskNames);
1043
1044 /* We need to create a map with the already created medias. This is
1045 * necessary, cause different snapshots could have the same
1046 * parents/parent chain. If a medium is in this map already, it isn't
1047 * cloned a second time, but simply used. */
1048 typedef std::map<Utf8Str, ComObjPtr<Medium> > TStrMediumMap;
1049 typedef std::pair<Utf8Str, ComObjPtr<Medium> > TStrMediumPair;
1050 TStrMediumMap map;
1051 size_t cDisks = 0;
1052 for (size_t i = 0; i < d->llMedias.size(); ++i)
1053 {
1054 const MEDIUMTASKCHAIN &mtc = d->llMedias.at(i);
1055 ComObjPtr<Medium> pNewParent;
1056 uint32_t uSrcParentIdx = UINT32_MAX;
1057 uint32_t uTrgParentIdx = UINT32_MAX;
1058 for (size_t a = mtc.chain.size(); a > 0; --a)
1059 {
1060 const MEDIUMTASK &mt = mtc.chain.at(a - 1);
1061 ComPtr<IMedium> pMedium = mt.pMedium;
1062
1063 Bstr bstrSrcName;
1064 rc = pMedium->COMGETTER(Name)(bstrSrcName.asOutParam());
1065 if (FAILED(rc)) throw rc;
1066
1067 rc = d->pProgress->SetNextOperation(BstrFmt(p->tr("Cloning Disk '%ls' ..."), bstrSrcName.raw()).raw(),
1068 mt.uWeight);
1069 if (FAILED(rc)) throw rc;
1070
1071 Bstr bstrSrcId;
1072 rc = pMedium->COMGETTER(Id)(bstrSrcId.asOutParam());
1073 if (FAILED(rc)) throw rc;
1074
1075 if (mtc.fAttachLinked)
1076 {
1077 IMedium *pTmp = pMedium;
1078 ComObjPtr<Medium> pLMedium = static_cast<Medium*>(pTmp);
1079 if (pLMedium.isNull())
1080 throw p->setError(VBOX_E_OBJECT_NOT_FOUND);
1081 ComObjPtr<Medium> pBase = pLMedium->i_getBase();
1082 if (pBase->i_isReadOnly())
1083 {
1084 ComObjPtr<Medium> pDiff;
1085 /* create the diff under the snapshot medium */
1086 trgLock.release();
1087 srcLock.release();
1088 rc = d->createDifferencingMedium(p, pLMedium, strTrgSnapshotFolder,
1089 newMedia, &pDiff);
1090 srcLock.acquire();
1091 trgLock.acquire();
1092 if (FAILED(rc)) throw rc;
1093 map.insert(TStrMediumPair(Utf8Str(bstrSrcId), pDiff));
1094 /* diff image has to be used... */
1095 pNewParent = pDiff;
1096 }
1097 else
1098 {
1099 /* Attach the medium directly, as its type is not
1100 * subject to diff creation. */
1101 newMedia.append(pLMedium);
1102 map.insert(TStrMediumPair(Utf8Str(bstrSrcId), pLMedium));
1103 pNewParent = pLMedium;
1104 }
1105 }
1106 else
1107 {
1108 /* Is a clone already there? */
1109 TStrMediumMap::iterator it = map.find(Utf8Str(bstrSrcId));
1110 if (it != map.end())
1111 pNewParent = it->second;
1112 else
1113 {
1114 ComPtr<IMediumFormat> pSrcFormat;
1115 rc = pMedium->COMGETTER(MediumFormat)(pSrcFormat.asOutParam());
1116 ULONG uSrcCaps = 0;
1117 com::SafeArray <MediumFormatCapabilities_T> mediumFormatCap;
1118 rc = pSrcFormat->COMGETTER(Capabilities)(ComSafeArrayAsOutParam(mediumFormatCap));
1119
1120 if (FAILED(rc)) throw rc;
1121 else
1122 {
1123 for (ULONG j = 0; j < mediumFormatCap.size(); j++)
1124 uSrcCaps |= mediumFormatCap[j];
1125 }
1126
1127 /* Default format? */
1128 Utf8Str strDefaultFormat;
1129 p->mParent->i_getDefaultHardDiskFormat(strDefaultFormat);
1130 Bstr bstrSrcFormat(strDefaultFormat);
1131
1132 ULONG srcVar = MediumVariant_Standard;
1133 com::SafeArray <MediumVariant_T> mediumVariant;
1134
1135 /* Is the source file based? */
1136 if ((uSrcCaps & MediumFormatCapabilities_File) == MediumFormatCapabilities_File)
1137 {
1138 /* Yes, just use the source format. Otherwise the defaults
1139 * will be used. */
1140 rc = pMedium->COMGETTER(Format)(bstrSrcFormat.asOutParam());
1141 if (FAILED(rc)) throw rc;
1142
1143 rc = pMedium->COMGETTER(Variant)(ComSafeArrayAsOutParam(mediumVariant));
1144 if (FAILED(rc)) throw rc;
1145 else
1146 {
1147 for (size_t j = 0; j < mediumVariant.size(); j++)
1148 srcVar |= mediumVariant[j];
1149 }
1150 }
1151
1152 Guid newId;
1153 newId.create();
1154 Utf8Str strNewName(bstrSrcName);
1155 if (!fKeepDiskNames)
1156 {
1157 Utf8Str strSrcTest = bstrSrcName;
1158 /* Check if we have to use another name. */
1159 if (!mt.strBaseName.isEmpty())
1160 strSrcTest = mt.strBaseName;
1161 strSrcTest.stripSuffix();
1162 /* If the old disk name was in {uuid} format we also
1163 * want the new name in this format, but with the
1164 * updated id of course. If the old disk was called
1165 * like the VM name, we change it to the new VM name.
1166 * For all other disks we rename them with this
1167 * template: "new name-disk1.vdi". */
1168 if (strSrcTest == strOldVMName)
1169 strNewName = Utf8StrFmt("%s%s", trgMCF.machineUserData.strName.c_str(),
1170 RTPathSuffix(Utf8Str(bstrSrcName).c_str()));
1171 else if ( strSrcTest.startsWith("{")
1172 && strSrcTest.endsWith("}"))
1173 {
1174 strSrcTest = strSrcTest.substr(1, strSrcTest.length() - 2);
1175
1176 Guid temp_guid(strSrcTest);
1177 if (temp_guid.isValid() && !temp_guid.isZero())
1178 strNewName = Utf8StrFmt("%s%s", newId.toStringCurly().c_str(),
1179 RTPathSuffix(strNewName.c_str()));
1180 }
1181 else
1182 strNewName = Utf8StrFmt("%s-disk%d%s", trgMCF.machineUserData.strName.c_str(), ++cDisks,
1183 RTPathSuffix(Utf8Str(bstrSrcName).c_str()));
1184 }
1185
1186 /* Check if this medium comes from the snapshot folder, if
1187 * so, put it there in the cloned machine as well.
1188 * Otherwise it goes to the machine folder. */
1189 Bstr bstrSrcPath;
1190 Utf8Str strFile = Utf8StrFmt("%s%c%s", strTrgMachineFolder.c_str(), RTPATH_DELIMITER, strNewName.c_str());
1191 rc = pMedium->COMGETTER(Location)(bstrSrcPath.asOutParam());
1192 if (FAILED(rc)) throw rc;
1193 if ( !bstrSrcPath.isEmpty()
1194 && RTPathStartsWith(Utf8Str(bstrSrcPath).c_str(), Utf8Str(bstrSrcSnapshotFolder).c_str())
1195 && (fKeepDiskNames || mt.strBaseName.isEmpty()))
1196 strFile = Utf8StrFmt("%s%c%s", strTrgSnapshotFolder.c_str(), RTPATH_DELIMITER, strNewName.c_str());
1197
1198 /* Start creating the clone. */
1199 ComObjPtr<Medium> pTarget;
1200 rc = pTarget.createObject();
1201 if (FAILED(rc)) throw rc;
1202
1203 rc = pTarget->init(p->mParent,
1204 Utf8Str(bstrSrcFormat),
1205 strFile,
1206 Guid::Empty /* empty media registry */);
1207 if (FAILED(rc)) throw rc;
1208
1209 /* Update the new uuid. */
1210 pTarget->i_updateId(newId);
1211
1212 srcLock.release();
1213 /* Do the disk cloning. */
1214 ComPtr<IProgress> progress2;
1215
1216 ComObjPtr<Medium> pLMedium = static_cast<Medium*>((IMedium*)pMedium);
1217 rc = pLMedium->i_cloneToEx(pTarget,
1218 srcVar,
1219 pNewParent,
1220 progress2.asOutParam(),
1221 uSrcParentIdx,
1222 uTrgParentIdx);
1223 if (FAILED(rc)) throw rc;
1224
1225 /* Wait until the async process has finished. */
1226 rc = d->pProgress->WaitForAsyncProgressCompletion(progress2);
1227 srcLock.acquire();
1228 if (FAILED(rc)) throw rc;
1229
1230 /* Check the result of the async process. */
1231 LONG iRc;
1232 rc = progress2->COMGETTER(ResultCode)(&iRc);
1233 if (FAILED(rc)) throw rc;
1234 /* If the thread of the progress object has an error, then
1235 * retrieve the error info from there, or it'll be lost. */
1236 if (FAILED(iRc))
1237 throw p->setError(ProgressErrorInfo(progress2));
1238 /* Remember created medium. */
1239 newMedia.append(pTarget);
1240 /* Get the medium type from the source and set it to the
1241 * new medium. */
1242 MediumType_T type;
1243 rc = pMedium->COMGETTER(Type)(&type);
1244 if (FAILED(rc)) throw rc;
1245 rc = pTarget->COMSETTER(Type)(type);
1246 if (FAILED(rc)) throw rc;
1247 map.insert(TStrMediumPair(Utf8Str(bstrSrcId), pTarget));
1248 /* register the new harddisk */
1249 {
1250 AutoWriteLock tlock(p->mParent->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
1251 rc = p->mParent->i_registerMedium(pTarget, &pTarget,
1252 DeviceType_HardDisk,
1253 tlock);
1254 if (FAILED(rc)) throw rc;
1255 }
1256 /* This medium becomes the parent of the next medium in the
1257 * chain. */
1258 pNewParent = pTarget;
1259 }
1260 }
1261 /* Save the current source medium index as the new parent
1262 * medium index. */
1263 uSrcParentIdx = mt.uIdx;
1264 /* Simply increase the target index. */
1265 ++uTrgParentIdx;
1266 }
1267
1268 Bstr bstrSrcId;
1269 rc = mtc.chain.first().pMedium->COMGETTER(Id)(bstrSrcId.asOutParam());
1270 if (FAILED(rc)) throw rc;
1271 Bstr bstrTrgId;
1272 rc = pNewParent->COMGETTER(Id)(bstrTrgId.asOutParam());
1273 if (FAILED(rc)) throw rc;
1274 /* update snapshot configuration */
1275 d->updateSnapshotStorageLists(trgMCF.llFirstSnapshot, bstrSrcId, bstrTrgId);
1276
1277 /* create new 'Current State' diff for caller defined place */
1278 if (mtc.fCreateDiffs)
1279 {
1280 const MEDIUMTASK &mt = mtc.chain.first();
1281 ComObjPtr<Medium> pLMedium = static_cast<Medium*>((IMedium*)mt.pMedium);
1282 if (pLMedium.isNull())
1283 throw p->setError(VBOX_E_OBJECT_NOT_FOUND);
1284 ComObjPtr<Medium> pBase = pLMedium->i_getBase();
1285 if (pBase->i_isReadOnly())
1286 {
1287 ComObjPtr<Medium> pDiff;
1288 trgLock.release();
1289 srcLock.release();
1290 rc = d->createDifferencingMedium(p, pNewParent, strTrgSnapshotFolder,
1291 newMedia, &pDiff);
1292 srcLock.acquire();
1293 trgLock.acquire();
1294 if (FAILED(rc)) throw rc;
1295 /* diff image has to be used... */
1296 pNewParent = pDiff;
1297 }
1298 else
1299 {
1300 /* Attach the medium directly, as its type is not
1301 * subject to diff creation. */
1302 newMedia.append(pNewParent);
1303 }
1304
1305 rc = pNewParent->COMGETTER(Id)(bstrTrgId.asOutParam());
1306 if (FAILED(rc)) throw rc;
1307 }
1308 /* update 'Current State' configuration */
1309 d->updateStorageLists(trgMCF.storageMachine.llStorageControllers, bstrSrcId, bstrTrgId);
1310 }
1311 /* Make sure all disks know of the new machine uuid. We do this last to
1312 * be able to change the medium type above. */
1313 for (size_t i = newMedia.size(); i > 0; --i)
1314 {
1315 const ComObjPtr<Medium> &pMedium = newMedia.at(i - 1);
1316 AutoCaller mac(pMedium);
1317 if (FAILED(mac.rc())) throw mac.rc();
1318 AutoWriteLock mlock(pMedium COMMA_LOCKVAL_SRC_POS);
1319 Guid uuid = d->pTrgMachine->mData->mUuid;
1320 if (d->options.contains(CloneOptions_Link))
1321 {
1322 ComObjPtr<Medium> pParent = pMedium->i_getParent();
1323 mlock.release();
1324 if (!pParent.isNull())
1325 {
1326 AutoCaller mac2(pParent);
1327 if (FAILED(mac2.rc())) throw mac2.rc();
1328 AutoReadLock mlock2(pParent COMMA_LOCKVAL_SRC_POS);
1329 if (pParent->i_getFirstRegistryMachineId(uuid))
1330 {
1331 mlock2.release();
1332 trgLock.release();
1333 srcLock.release();
1334 p->mParent->i_markRegistryModified(uuid);
1335 srcLock.acquire();
1336 trgLock.acquire();
1337 mlock2.acquire();
1338 }
1339 }
1340 mlock.acquire();
1341 }
1342 pMedium->i_addRegistry(uuid, false /* fRecurse */);
1343 }
1344 /* Check if a snapshot folder is necessary and if so doesn't already
1345 * exists. */
1346 if ( !d->llSaveStateFiles.isEmpty()
1347 && !RTDirExists(strTrgSnapshotFolder.c_str()))
1348 {
1349 int vrc = RTDirCreateFullPath(strTrgSnapshotFolder.c_str(), 0700);
1350 if (RT_FAILURE(vrc))
1351 throw p->setError(VBOX_E_IPRT_ERROR,
1352 p->tr("Could not create snapshots folder '%s' (%Rrc)"),
1353 strTrgSnapshotFolder.c_str(), vrc);
1354 }
1355 /* Clone all save state files. */
1356 for (size_t i = 0; i < d->llSaveStateFiles.size(); ++i)
1357 {
1358 SAVESTATETASK sst = d->llSaveStateFiles.at(i);
1359 const Utf8Str &strTrgSaveState = Utf8StrFmt("%s%c%s", strTrgSnapshotFolder.c_str(), RTPATH_DELIMITER,
1360 RTPathFilename(sst.strSaveStateFile.c_str()));
1361
1362 /* Move to next sub-operation. */
1363 rc = d->pProgress->SetNextOperation(BstrFmt(p->tr("Copy save state file '%s' ..."),
1364 RTPathFilename(sst.strSaveStateFile.c_str())).raw(), sst.uWeight);
1365 if (FAILED(rc)) throw rc;
1366 /* Copy the file only if it was not copied already. */
1367 if (!newFiles.contains(strTrgSaveState.c_str()))
1368 {
1369 int vrc = RTFileCopyEx(sst.strSaveStateFile.c_str(), strTrgSaveState.c_str(), 0,
1370 MachineCloneVMPrivate::copyStateFileProgress, &d->pProgress);
1371 if (RT_FAILURE(vrc))
1372 throw p->setError(VBOX_E_IPRT_ERROR,
1373 p->tr("Could not copy state file '%s' to '%s' (%Rrc)"),
1374 sst.strSaveStateFile.c_str(), strTrgSaveState.c_str(), vrc);
1375 newFiles.append(strTrgSaveState);
1376 }
1377 /* Update the path in the configuration either for the current
1378 * machine state or the snapshots. */
1379 if (!sst.snapshotUuid.isValid() || sst.snapshotUuid.isZero())
1380 trgMCF.strStateFile = strTrgSaveState;
1381 else
1382 d->updateStateFile(trgMCF.llFirstSnapshot, sst.snapshotUuid, strTrgSaveState);
1383 }
1384
1385 {
1386 rc = d->pProgress->SetNextOperation(BstrFmt(p->tr("Create Machine Clone '%s' ..."),
1387 trgMCF.machineUserData.strName.c_str()).raw(), 1);
1388 if (FAILED(rc)) throw rc;
1389 /* After modifying the new machine config, we can copy the stuff
1390 * over to the new machine. The machine have to be mutable for
1391 * this. */
1392 rc = d->pTrgMachine->i_checkStateDependency(p->MutableStateDep);
1393 if (FAILED(rc)) throw rc;
1394 rc = d->pTrgMachine->i_loadMachineDataFromSettings(trgMCF, &d->pTrgMachine->mData->mUuid);
1395 if (FAILED(rc)) throw rc;
1396 /* save all VM data */
1397 bool fNeedsGlobalSaveSettings = false;
1398 rc = d->pTrgMachine->i_saveSettings(&fNeedsGlobalSaveSettings, Machine::SaveS_Force);
1399 if (FAILED(rc)) throw rc;
1400 /* Release all locks */
1401 trgLock.release();
1402 srcLock.release();
1403 if (fNeedsGlobalSaveSettings)
1404 {
1405 /* save the global settings; for that we should hold only the
1406 * VirtualBox lock */
1407 AutoWriteLock vlock(p->mParent COMMA_LOCKVAL_SRC_POS);
1408 rc = p->mParent->i_saveSettings();
1409 if (FAILED(rc)) throw rc;
1410 }
1411 }
1412
1413 /* Any additional machines need saving? */
1414 p->mParent->i_saveModifiedRegistries();
1415 }
1416 catch (HRESULT rc2)
1417 {
1418 rc = rc2;
1419 }
1420 catch (...)
1421 {
1422 rc = VirtualBoxBase::handleUnexpectedExceptions(p, RT_SRC_POS);
1423 }
1424
1425 MultiResult mrc(rc);
1426 /* Cleanup on failure (CANCEL also) */
1427 if (FAILED(rc))
1428 {
1429 int vrc = VINF_SUCCESS;
1430 /* Delete all created files. */
1431 for (size_t i = 0; i < newFiles.size(); ++i)
1432 {
1433 vrc = RTFileDelete(newFiles.at(i).c_str());
1434 if (RT_FAILURE(vrc))
1435 mrc = p->setError(VBOX_E_IPRT_ERROR, p->tr("Could not delete file '%s' (%Rrc)"), newFiles.at(i).c_str(), vrc);
1436 }
1437 /* Delete all already created medias. (Reverse, cause there could be
1438 * parent->child relations.) */
1439 for (size_t i = newMedia.size(); i > 0; --i)
1440 {
1441 const ComObjPtr<Medium> &pMedium = newMedia.at(i - 1);
1442 mrc = pMedium->i_deleteStorage(NULL /* aProgress */,
1443 true /* aWait */);
1444 pMedium->Close();
1445 }
1446 /* Delete the snapshot folder when not empty. */
1447 if (!strTrgSnapshotFolder.isEmpty())
1448 RTDirRemove(strTrgSnapshotFolder.c_str());
1449 /* Delete the machine folder when not empty. */
1450 RTDirRemove(strTrgMachineFolder.c_str());
1451
1452 /* Must save the modified registries */
1453 p->mParent->i_saveModifiedRegistries();
1454 }
1455
1456 return mrc;
1457}
1458
1459void MachineCloneVM::destroy()
1460{
1461 delete this;
1462}
1463
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