VirtualBox

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

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

scm --update-copyright-year

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