VirtualBox

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

Last change on this file since 38406 was 38264, checked in by vboxsync, 14 years ago

Main-CloneVM: Respected the modified state of the source VM. Set to unmodified when a new "current state" is created in the "machineandchildren" case.

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

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette