VirtualBox

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

Last change on this file since 73768 was 73743, checked in by vboxsync, 6 years ago

Main/Progress+Appliance+Machine: Turn IProgress::waitForAsyncProgressCompletion into an internal method. It is not needed by any client code outside the API, it's simply is too special. Also include the error propagation which it originally skipped (leding to reduntant code in the calling code). Remove a replicated version of it from the Appliance code which seems to be the half-forgotten ancestor of the method in IProgress. Adapt the users of the method to the new internal method, which made the code easier to read.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 64.9 KB
Line 
1/* $Id: MachineImplCloneVM.cpp 73743 2018-08-17 17:56:34Z 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->i_waitForOtherProgressCompletion(progress2);
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.

© 2023 Oracle
ContactPrivacy policyTerms of Use