VirtualBox

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

Last change on this file since 60952 was 60627, checked in by vboxsync, 9 years ago

Main/Medium: When creating a medium lock list, explicitly state which object should be locked for writing (in some case it isn't the object for which the lock list is requested). This is especially important for backward medium merge operation, where the target isn't that object for backward merging. All other changes are simply adjusting code to the changed method parameter list.

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