VirtualBox

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

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

Main/Main/src-server/MachineImplCloneVM: fix a collection of bugs around losing saved state during cloning (file was copied, but not used in "current state" as it should be) and incorrect value of the "current state modified" flag of the clone.

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

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