VirtualBox

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

Last change on this file since 75819 was 74804, checked in by vboxsync, 6 years ago

Main/Progress: Split into safe public interface and a private one which is used purely by API implementation code (for updating). Needed quite a few minor adjustments elsewhere.

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