VirtualBox

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

Last change on this file since 67954 was 61009, checked in by vboxsync, 8 years ago

Main: big settings cleanup and writing optimization. Moved constructors/equality/default checks into the .cpp file, and write only settings which aren't at the default value. Greatly reduces the effort needed to write everything out, especially when a lot of snapshots have to be dealt with. Move the storage controllers to the hardware settings, where they always belonged. No change to the XML file (yet). Lots of settings related cleanups in the API code.

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