VirtualBox

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

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

Main-CloneVM: invert it

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 45.4 KB
Line 
1/* $Id: MachineImplCloneVM.cpp 38061 2011-07-19 09:50:33Z 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.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
28#include <VBox/com/list.h>
29#include <VBox/com/MultiResult.h>
30
31// typedefs
32/////////////////////////////////////////////////////////////////////////////
33
34typedef struct
35{
36 Utf8Str strBaseName;
37 ComPtr<IMedium> pMedium;
38 ULONG uWeight;
39} MEDIUMTASK;
40
41typedef struct
42{
43 RTCList<MEDIUMTASK> chain;
44 bool fCreateDiffs;
45 bool fAttachLinked;
46} MEDIUMTASKCHAIN;
47
48typedef struct
49{
50 Guid snapshotUuid;
51 Utf8Str strSaveStateFile;
52 ULONG uWeight;
53} SAVESTATETASK;
54
55// The private class
56/////////////////////////////////////////////////////////////////////////////
57
58struct MachineCloneVMPrivate
59{
60 MachineCloneVMPrivate(MachineCloneVM *a_q, ComObjPtr<Machine> &a_pSrcMachine, ComObjPtr<Machine> &a_pTrgMachine, CloneMode_T a_mode, const RTCList<CloneOptions_T> &opts)
61 : q_ptr(a_q)
62 , p(a_pSrcMachine)
63 , pSrcMachine(a_pSrcMachine)
64 , pTrgMachine(a_pTrgMachine)
65 , mode(a_mode)
66 , options(opts)
67 {}
68
69 /* Thread management */
70 int startWorker()
71 {
72 return RTThreadCreate(NULL,
73 MachineCloneVMPrivate::workerThread,
74 static_cast<void*>(this),
75 0,
76 RTTHREADTYPE_MAIN_WORKER,
77 0,
78 "MachineClone");
79 }
80
81 static int workerThread(RTTHREAD /* Thread */, void *pvUser)
82 {
83 MachineCloneVMPrivate *pTask = static_cast<MachineCloneVMPrivate*>(pvUser);
84 AssertReturn(pTask, VERR_INVALID_POINTER);
85
86 HRESULT rc = pTask->q_ptr->run();
87
88 pTask->pProgress->notifyComplete(rc);
89
90 pTask->q_ptr->destroy();
91
92 return VINF_SUCCESS;
93 }
94
95 /* Private helper methods */
96 HRESULT createMachineList(const ComPtr<ISnapshot> &pSnapshot, RTCList< ComObjPtr<Machine> > &machineList) const;
97 settings::Snapshot findSnapshot(settings::MachineConfigFile *pMCF, const settings::SnapshotsList &snl, const Guid &id) const;
98 void updateMACAddresses(settings::NetworkAdaptersList &nwl) const;
99 void updateMACAddresses(settings::SnapshotsList &sl) const;
100 void updateStorageLists(settings::StorageControllersList &sc, const Bstr &bstrOldId, const Bstr &bstrNewId) const;
101 void updateSnapshotStorageLists(settings::SnapshotsList &sl, const Bstr &bstrOldId, const Bstr &bstrNewId) const;
102 void updateStateFile(settings::SnapshotsList &snl, const Guid &id, const Utf8Str &strFile) const;
103 static int copyStateFileProgress(unsigned uPercentage, void *pvUser);
104
105 /* Private q and parent pointer */
106 MachineCloneVM *q_ptr;
107 ComObjPtr<Machine> p;
108
109 /* Private helper members */
110 ComObjPtr<Machine> pSrcMachine;
111 ComObjPtr<Machine> pTrgMachine;
112 ComPtr<IMachine> pOldMachineState;
113 ComObjPtr<Progress> pProgress;
114 Guid snapshotId;
115 CloneMode_T mode;
116 RTCList<CloneOptions_T> options;
117 RTCList<MEDIUMTASKCHAIN> llMedias;
118 RTCList<SAVESTATETASK> llSaveStateFiles; /* Snapshot UUID -> File path */
119};
120
121HRESULT MachineCloneVMPrivate::createMachineList(const ComPtr<ISnapshot> &pSnapshot, RTCList< ComObjPtr<Machine> > &machineList) const
122{
123 HRESULT rc = S_OK;
124 Bstr name;
125 rc = pSnapshot->COMGETTER(Name)(name.asOutParam());
126 if (FAILED(rc)) return rc;
127
128 ComPtr<IMachine> pMachine;
129 rc = pSnapshot->COMGETTER(Machine)(pMachine.asOutParam());
130 if (FAILED(rc)) return rc;
131 machineList.append((Machine*)(IMachine*)pMachine);
132
133 SafeIfaceArray<ISnapshot> sfaChilds;
134 rc = pSnapshot->COMGETTER(Children)(ComSafeArrayAsOutParam(sfaChilds));
135 if (FAILED(rc)) return rc;
136 for (size_t i = 0; i < sfaChilds.size(); ++i)
137 {
138 rc = createMachineList(sfaChilds[i], machineList);
139 if (FAILED(rc)) return rc;
140 }
141
142 return rc;
143}
144
145settings::Snapshot MachineCloneVMPrivate::findSnapshot(settings::MachineConfigFile *pMCF, const settings::SnapshotsList &snl, const Guid &id) const
146{
147 settings::SnapshotsList::const_iterator it;
148 for (it = snl.begin(); it != snl.end(); ++it)
149 {
150 if (it->uuid == id)
151 return *it;
152 else if (!it->llChildSnapshots.empty())
153 return findSnapshot(pMCF, it->llChildSnapshots, id);
154 }
155 return settings::Snapshot();
156}
157
158void MachineCloneVMPrivate::updateMACAddresses(settings::NetworkAdaptersList &nwl) const
159{
160 const bool fNotNAT = options.contains(CloneOptions_KeepNATMACs);
161 settings::NetworkAdaptersList::iterator it;
162 for (it = nwl.begin(); it != nwl.end(); ++it)
163 {
164 if ( fNotNAT
165 && it->mode == NetworkAttachmentType_NAT)
166 continue;
167 Host::generateMACAddress(it->strMACAddress);
168 }
169}
170
171void MachineCloneVMPrivate::updateMACAddresses(settings::SnapshotsList &sl) const
172{
173 settings::SnapshotsList::iterator it;
174 for (it = sl.begin(); it != sl.end(); ++it)
175 {
176 updateMACAddresses(it->hardware.llNetworkAdapters);
177 if (!it->llChildSnapshots.empty())
178 updateMACAddresses(it->llChildSnapshots);
179 }
180}
181
182void MachineCloneVMPrivate::updateStorageLists(settings::StorageControllersList &sc, const Bstr &bstrOldId, const Bstr &bstrNewId) const
183{
184 settings::StorageControllersList::iterator it3;
185 for (it3 = sc.begin();
186 it3 != sc.end();
187 ++it3)
188 {
189 settings::AttachedDevicesList &llAttachments = it3->llAttachedDevices;
190 settings::AttachedDevicesList::iterator it4;
191 for (it4 = llAttachments.begin();
192 it4 != llAttachments.end();
193 ++it4)
194 {
195 if ( it4->deviceType == DeviceType_HardDisk
196 && it4->uuid == bstrOldId)
197 {
198 it4->uuid = bstrNewId;
199 }
200 }
201 }
202}
203
204void MachineCloneVMPrivate::updateSnapshotStorageLists(settings::SnapshotsList &sl, const Bstr &bstrOldId, const Bstr &bstrNewId) const
205{
206 settings::SnapshotsList::iterator it;
207 for ( it = sl.begin();
208 it != sl.end();
209 ++it)
210 {
211 updateStorageLists(it->storage.llStorageControllers, bstrOldId, bstrNewId);
212 if (!it->llChildSnapshots.empty())
213 updateSnapshotStorageLists(it->llChildSnapshots, bstrOldId, bstrNewId);
214 }
215}
216
217void MachineCloneVMPrivate::updateStateFile(settings::SnapshotsList &snl, const Guid &id, const Utf8Str &strFile) const
218{
219 settings::SnapshotsList::iterator it;
220 for (it = snl.begin(); it != snl.end(); ++it)
221 {
222 if (it->uuid == id)
223 it->strStateFile = strFile;
224 else if (!it->llChildSnapshots.empty())
225 updateStateFile(it->llChildSnapshots, id, strFile);
226 }
227}
228
229/* static */
230int MachineCloneVMPrivate::copyStateFileProgress(unsigned uPercentage, void *pvUser)
231{
232 ComObjPtr<Progress> pProgress = *static_cast< ComObjPtr<Progress>* >(pvUser);
233
234 BOOL fCanceled = false;
235 HRESULT rc = pProgress->COMGETTER(Canceled)(&fCanceled);
236 if (FAILED(rc)) return VERR_GENERAL_FAILURE;
237 /* If canceled by the user tell it to the copy operation. */
238 if (fCanceled) return VERR_CANCELLED;
239 /* Set the new process. */
240 rc = pProgress->SetCurrentOperationProgress(uPercentage);
241 if (FAILED(rc)) return VERR_GENERAL_FAILURE;
242
243 return VINF_SUCCESS;
244}
245
246// The public class
247/////////////////////////////////////////////////////////////////////////////
248
249MachineCloneVM::MachineCloneVM(ComObjPtr<Machine> pSrcMachine, ComObjPtr<Machine> pTrgMachine, CloneMode_T mode, const RTCList<CloneOptions_T> &opts)
250 : d_ptr(new MachineCloneVMPrivate(this, pSrcMachine, pTrgMachine, mode, opts))
251{
252}
253
254MachineCloneVM::~MachineCloneVM()
255{
256 delete d_ptr;
257}
258
259HRESULT MachineCloneVM::start(IProgress **pProgress)
260{
261 DPTR(MachineCloneVM);
262 ComObjPtr<Machine> &p = d->p;
263
264 HRESULT rc;
265 try
266 {
267 /** @todo r=klaus this code cannot deal with someone crazy specifying
268 * IMachine corresponding to a mutable machine as d->pSrcMachine */
269 if (d->pSrcMachine->isSessionMachine())
270 throw E_FAIL;
271
272 /* Handle the special case that someone is requesting a _full_ clone
273 * with all snapshots (and the current state), but uses a snapshot
274 * machine (and not the current one) as source machine. In this case we
275 * just replace the source (snapshot) machine with the current machine. */
276 if ( d->mode == CloneMode_AllStates
277 && d->pSrcMachine->isSnapshotMachine())
278 {
279 Bstr bstrSrcMachineId;
280 rc = d->pSrcMachine->COMGETTER(Id)(bstrSrcMachineId.asOutParam());
281 if (FAILED(rc)) throw rc;
282 ComPtr<IMachine> newSrcMachine;
283 rc = d->pSrcMachine->getVirtualBox()->FindMachine(bstrSrcMachineId.raw(), newSrcMachine.asOutParam());
284 if (FAILED(rc)) throw rc;
285 d->pSrcMachine = (Machine*)(IMachine*)newSrcMachine;
286 }
287
288 bool fSubtreeIncludesCurrent = false;
289 ComObjPtr<Machine> pCurrState;
290 if (d->mode == CloneMode_MachineAndChildStates)
291 {
292 if (d->pSrcMachine->isSnapshotMachine())
293 {
294 /* find machine object for current snapshot of current state */
295 Bstr bstrSrcMachineId;
296 rc = d->pSrcMachine->COMGETTER(Id)(bstrSrcMachineId.asOutParam());
297 if (FAILED(rc)) throw rc;
298 ComPtr<IMachine> pCurr;
299 rc = d->pSrcMachine->getVirtualBox()->FindMachine(bstrSrcMachineId.raw(), pCurr.asOutParam());
300 if (FAILED(rc)) throw rc;
301 if (pCurr.isNull())
302 throw E_FAIL;
303 pCurrState = (Machine *)(IMachine *)pCurr;
304 ComPtr<ISnapshot> pSnapshot;
305 rc = pCurrState->COMGETTER(CurrentSnapshot)(pSnapshot.asOutParam());
306 if (FAILED(rc)) throw rc;
307 if (pSnapshot.isNull())
308 throw E_FAIL;
309 ComPtr<IMachine> pCurrSnapMachine;
310 rc = pSnapshot->COMGETTER(Machine)(pCurrSnapMachine.asOutParam());
311 if (FAILED(rc)) throw rc;
312 if (pCurrSnapMachine.isNull())
313 throw E_FAIL;
314
315 /* now check if there is a parent chain which leads to the
316 * snapshot machine defining the subtree. */
317 while (!pSnapshot.isNull())
318 {
319 ComPtr<IMachine> pSnapMachine;
320 rc = pSnapshot->COMGETTER(Machine)(pSnapMachine.asOutParam());
321 if (FAILED(rc)) throw rc;
322 if (pSnapMachine.isNull())
323 throw E_FAIL;
324 if (pSnapMachine == d->pSrcMachine)
325 {
326 fSubtreeIncludesCurrent = true;
327 break;
328 }
329 rc = pSnapshot->COMGETTER(Parent)(pSnapshot.asOutParam());
330 }
331 }
332 else
333 {
334 /* If the subtree is only the Current State simply use the
335 * 'machine' case for cloning. It is easier to understand. */
336 d->mode = CloneMode_MachineState;
337 }
338 }
339
340 /* Lock the target machine early (so nobody mess around with it in the meantime). */
341 AutoWriteLock trgLock(d->pTrgMachine COMMA_LOCKVAL_SRC_POS);
342
343 if (d->pSrcMachine->isSnapshotMachine())
344 d->snapshotId = d->pSrcMachine->getSnapshotId();
345
346 /* Add the current machine and all snapshot machines below this machine
347 * in a list for further processing. */
348 RTCList< ComObjPtr<Machine> > machineList;
349
350 /* Include current state? */
351 if ( d->mode == CloneMode_MachineState
352 || d->mode == CloneMode_AllStates)
353 machineList.append(d->pSrcMachine);
354 /* Should be done a depth copy with all child snapshots? */
355 if ( d->mode == CloneMode_MachineAndChildStates
356 || d->mode == CloneMode_AllStates)
357 {
358 ULONG cSnapshots = 0;
359 rc = d->pSrcMachine->COMGETTER(SnapshotCount)(&cSnapshots);
360 if (FAILED(rc)) throw rc;
361 if (cSnapshots > 0)
362 {
363 Utf8Str id;
364 if (d->mode == CloneMode_MachineAndChildStates)
365 id = d->snapshotId.toString();
366 ComPtr<ISnapshot> pSnapshot;
367 rc = d->pSrcMachine->FindSnapshot(Bstr(id).raw(), pSnapshot.asOutParam());
368 if (FAILED(rc)) throw rc;
369 rc = d->createMachineList(pSnapshot, machineList);
370 if (FAILED(rc)) throw rc;
371 if (d->mode == CloneMode_MachineAndChildStates)
372 {
373 if (fSubtreeIncludesCurrent)
374 {
375 /* zap d->snapshotId because there is no need to
376 * create a new current state. */
377 d->snapshotId.clear();
378 if (pCurrState.isNull())
379 throw E_FAIL;
380 machineList.append(pCurrState);
381 }
382 else
383 {
384 rc = pSnapshot->COMGETTER(Machine)(d->pOldMachineState.asOutParam());
385 if (FAILED(rc)) throw rc;
386 }
387 }
388 }
389 }
390
391 /* Go over every machine and walk over every attachment this machine has. */
392 ULONG uCount = 2; /* One init task and the machine creation. */
393 ULONG uTotalWeight = 2; /* The init task and the machine creation is worth one. */
394 for (size_t i = 0; i < machineList.size(); ++i)
395 {
396 ComObjPtr<Machine> machine = machineList.at(i);
397 /* If this is the Snapshot Machine we want to clone, we need to
398 * create a new diff file for the new "current state". */
399 bool fCreateDiffs = false;
400 if (machine == d->pOldMachineState)
401 fCreateDiffs = true;
402 /* If we want to create a linked clone just attach the medium
403 * associated with the snapshot. The rest is taken care of by
404 * attach already, so no need to duplicate this. */
405 bool fAttachLinked = false;
406 if (d->options.contains(CloneOptions_Link))
407 fAttachLinked = true;
408 SafeIfaceArray<IMediumAttachment> sfaAttachments;
409 rc = machine->COMGETTER(MediumAttachments)(ComSafeArrayAsOutParam(sfaAttachments));
410 if (FAILED(rc)) throw rc;
411 /* Add all attachments (and their parents) of the different
412 * machines to a worker list. */
413 for (size_t a = 0; a < sfaAttachments.size(); ++a)
414 {
415 const ComPtr<IMediumAttachment> &pAtt = sfaAttachments[a];
416 DeviceType_T type;
417 rc = pAtt->COMGETTER(Type)(&type);
418 if (FAILED(rc)) throw rc;
419
420 /* Only harddisk's are of interest. */
421 if (type != DeviceType_HardDisk)
422 continue;
423
424 /* Valid medium attached? */
425 ComPtr<IMedium> pSrcMedium;
426 rc = pAtt->COMGETTER(Medium)(pSrcMedium.asOutParam());
427 if (FAILED(rc)) throw rc;
428 if (pSrcMedium.isNull())
429 continue;
430
431 /* Build up a child->parent list of this attachment. (Note: we are
432 * not interested of any child's not attached to this VM. So this
433 * will not create a full copy of the base/child relationship.) */
434 MEDIUMTASKCHAIN mtc;
435 mtc.fCreateDiffs = fCreateDiffs;
436 mtc.fAttachLinked = fAttachLinked;
437
438 /* If the current state without any snapshots is cloned, we
439 * don't need any diff images in the new clone. Add the last
440 * medium to the list of medias to create only (the clone
441 * operation of IMedium will create a merged copy
442 * automatically). */
443 if (d->mode == CloneMode_MachineState)
444 {
445 /* Refresh the state so that the file size get read. */
446 MediumState_T e;
447 rc = pSrcMedium->RefreshState(&e);
448 if (FAILED(rc)) throw rc;
449 LONG64 lSize;
450 rc = pSrcMedium->COMGETTER(Size)(&lSize);
451 if (FAILED(rc)) throw rc;
452
453 ComPtr<IMedium> pBaseMedium;
454 rc = pSrcMedium->COMGETTER(Base)(pBaseMedium.asOutParam());
455 if (FAILED(rc)) throw rc;
456
457 MEDIUMTASK mt;
458
459 Bstr bstrBaseName;
460 rc = pBaseMedium->COMGETTER(Name)(bstrBaseName.asOutParam());
461 if (FAILED(rc)) throw rc;
462
463 /* Save the base name. */
464 mt.strBaseName = bstrBaseName;
465
466 /* Save the current medium, for later cloning. */
467 mt.pMedium = pSrcMedium;
468 if (fAttachLinked)
469 mt.uWeight = 0; /* dummy */
470 else
471 mt.uWeight = (lSize + _1M - 1) / _1M;
472 mtc.chain.append(mt);
473 }
474 else
475 {
476 /** @todo r=klaus this puts way too many images in the list
477 * when cloning a snapshot (sub)tree, which means that more
478 * images are cloned than necessary. It is just the easiest
479 * way to get a working VM, as getting the image
480 * parent/child relationships right for only the bare
481 * minimum cloning is rather tricky. */
482 while (!pSrcMedium.isNull())
483 {
484 /* Refresh the state so that the file size get read. */
485 MediumState_T e;
486 rc = pSrcMedium->RefreshState(&e);
487 if (FAILED(rc)) throw rc;
488 LONG64 lSize;
489 rc = pSrcMedium->COMGETTER(Size)(&lSize);
490 if (FAILED(rc)) throw rc;
491
492 /* Save the current medium, for later cloning. */
493 MEDIUMTASK mt;
494 mt.pMedium = pSrcMedium;
495 mt.uWeight = (lSize + _1M - 1) / _1M;
496 mtc.chain.append(mt);
497
498 /* Query next parent. */
499 rc = pSrcMedium->COMGETTER(Parent)(pSrcMedium.asOutParam());
500 if (FAILED(rc)) throw rc;
501 }
502 }
503
504 if (fAttachLinked)
505 {
506 /* Implicit diff creation as part of attach is a pretty cheap
507 * operation, and does only need one operation per attachment. */
508 ++uCount;
509 uTotalWeight += 1; /* 1MB per attachment */
510 }
511 else
512 {
513 /* Currently the copying of diff images involves reading at least
514 * the biggest parent in the previous chain. So even if the new
515 * diff image is small in size, it could need some time to create
516 * it. Adding the biggest size in the chain should balance this a
517 * little bit more, i.e. the weight is the sum of the data which
518 * needs to be read and written. */
519 uint64_t uMaxSize = 0;
520 for (size_t e = mtc.chain.size(); e > 0; --e)
521 {
522 MEDIUMTASK &mt = mtc.chain.at(e - 1);
523 mt.uWeight += uMaxSize;
524
525 /* Calculate progress data */
526 ++uCount;
527 uTotalWeight += mt.uWeight;
528
529 /* Save the max size for better weighting of diff image
530 * creation. */
531 uMaxSize = RT_MAX(uMaxSize, mt.uWeight);
532 }
533 }
534 d->llMedias.append(mtc);
535 }
536 Bstr bstrSrcSaveStatePath;
537 rc = machine->COMGETTER(StateFilePath)(bstrSrcSaveStatePath.asOutParam());
538 if (FAILED(rc)) throw rc;
539 if (!bstrSrcSaveStatePath.isEmpty())
540 {
541 SAVESTATETASK sst;
542 sst.snapshotUuid = machine->getSnapshotId();
543 sst.strSaveStateFile = bstrSrcSaveStatePath;
544 uint64_t cbSize;
545 int vrc = RTFileQuerySize(sst.strSaveStateFile.c_str(), &cbSize);
546 if (RT_FAILURE(vrc))
547 throw p->setError(VBOX_E_IPRT_ERROR, p->tr("Could not query file size of '%s' (%Rrc)"), sst.strSaveStateFile.c_str(), vrc);
548 /* same rule as above: count both the data which needs to
549 * be read and written */
550 sst.uWeight = 2 * (cbSize + _1M - 1) / _1M;
551 d->llSaveStateFiles.append(sst);
552 ++uCount;
553 uTotalWeight += sst.uWeight;
554 }
555 }
556
557 rc = d->pProgress.createObject();
558 if (FAILED(rc)) throw rc;
559 rc = d->pProgress->init(p->getVirtualBox(),
560 static_cast<IMachine*>(d->pSrcMachine) /* aInitiator */,
561 Bstr(p->tr("Cloning Machine")).raw(),
562 true /* fCancellable */,
563 uCount,
564 uTotalWeight,
565 Bstr(p->tr("Initialize Cloning")).raw(),
566 1);
567 if (FAILED(rc)) throw rc;
568
569 int vrc = d->startWorker();
570
571 if (RT_FAILURE(vrc))
572 p->setError(VBOX_E_IPRT_ERROR, "Could not create machine clone thread (%Rrc)", vrc);
573 }
574 catch (HRESULT rc2)
575 {
576 rc = rc2;
577 }
578
579 if (SUCCEEDED(rc))
580 d->pProgress.queryInterfaceTo(pProgress);
581
582 return rc;
583}
584
585HRESULT MachineCloneVM::run()
586{
587 DPTR(MachineCloneVM);
588 ComObjPtr<Machine> &p = d->p;
589
590 AutoCaller autoCaller(p);
591 if (FAILED(autoCaller.rc())) return autoCaller.rc();
592
593 AutoReadLock srcLock(p COMMA_LOCKVAL_SRC_POS);
594 AutoWriteLock trgLock(d->pTrgMachine COMMA_LOCKVAL_SRC_POS);
595
596 HRESULT rc = S_OK;
597
598 /*
599 * Todo:
600 * - What about log files?
601 */
602
603 /* Where should all the media go? */
604 Utf8Str strTrgSnapshotFolder;
605 Utf8Str strTrgMachineFolder = d->pTrgMachine->getSettingsFileFull();
606 strTrgMachineFolder.stripFilename();
607
608 RTCList<ComObjPtr<Medium> > newMedia; /* All created images */
609 RTCList<Utf8Str> newFiles; /* All extra created files (save states, ...) */
610 try
611 {
612 /* Copy all the configuration from this machine to an empty
613 * configuration dataset. */
614 settings::MachineConfigFile trgMCF = *d->pSrcMachine->mData->pMachineConfigFile;
615
616 /* Reset media registry. */
617 trgMCF.mediaRegistry.llHardDisks.clear();
618 /* If we got a valid snapshot id, replace the hardware/storage section
619 * with the stuff from the snapshot. */
620 settings::Snapshot sn;
621 if (!d->snapshotId.isEmpty())
622 sn = d->findSnapshot(&trgMCF, trgMCF.llFirstSnapshot, d->snapshotId);
623
624 if (d->mode == CloneMode_MachineState)
625 {
626 if (!sn.uuid.isEmpty())
627 {
628 trgMCF.hardwareMachine = sn.hardware;
629 trgMCF.storageMachine = sn.storage;
630 }
631
632 /* Remove any hint on snapshots. */
633 trgMCF.llFirstSnapshot.clear();
634 trgMCF.uuidCurrentSnapshot.clear();
635 }
636 else if ( d->mode == CloneMode_MachineAndChildStates
637 && !sn.uuid.isEmpty())
638 {
639 /* Copy the snapshot data to the current machine. */
640 trgMCF.hardwareMachine = sn.hardware;
641 trgMCF.storageMachine = sn.storage;
642
643 /* The snapshot will be the root one. */
644 trgMCF.uuidCurrentSnapshot = sn.uuid;
645 trgMCF.llFirstSnapshot.clear();
646 trgMCF.llFirstSnapshot.push_back(sn);
647 }
648
649 /* Generate new MAC addresses for all machines when not forbidden. */
650 if (!d->options.contains(CloneOptions_KeepAllMACs))
651 {
652 d->updateMACAddresses(trgMCF.hardwareMachine.llNetworkAdapters);
653 d->updateMACAddresses(trgMCF.llFirstSnapshot);
654 }
655
656 /* When the current snapshot folder is absolute we reset it to the
657 * default relative folder. */
658 if (RTPathStartsWithRoot(trgMCF.machineUserData.strSnapshotFolder.c_str()))
659 trgMCF.machineUserData.strSnapshotFolder = "Snapshots";
660 trgMCF.strStateFile = "";
661 /* Force writing of setting file. */
662 trgMCF.fCurrentStateModified = true;
663 /* Set the new name. */
664 const Utf8Str strOldVMName = trgMCF.machineUserData.strName;
665 trgMCF.machineUserData.strName = d->pTrgMachine->mUserData->s.strName;
666 trgMCF.uuid = d->pTrgMachine->mData->mUuid;
667
668 Bstr bstrSrcSnapshotFolder;
669 rc = d->pSrcMachine->COMGETTER(SnapshotFolder)(bstrSrcSnapshotFolder.asOutParam());
670 if (FAILED(rc)) throw rc;
671 /* The absolute name of the snapshot folder. */
672 strTrgSnapshotFolder = Utf8StrFmt("%s%c%s", strTrgMachineFolder.c_str(), RTPATH_DELIMITER, trgMCF.machineUserData.strSnapshotFolder.c_str());
673
674 /* Should we rename the disk names. */
675 bool fKeepDiskNames = d->options.contains(CloneOptions_KeepDiskNames);
676
677 /* We need to create a map with the already created medias. This is
678 * necessary, cause different snapshots could have the same
679 * parents/parent chain. If a medium is in this map already, it isn't
680 * cloned a second time, but simply used. */
681 typedef std::map<Utf8Str, ComObjPtr<Medium> > TStrMediumMap;
682 typedef std::pair<Utf8Str, ComObjPtr<Medium> > TStrMediumPair;
683 TStrMediumMap map;
684 GuidList llRegistriesThatNeedSaving;
685 size_t cDisks = 0;
686 for (size_t i = 0; i < d->llMedias.size(); ++i)
687 {
688 const MEDIUMTASKCHAIN &mtc = d->llMedias.at(i);
689 ComObjPtr<Medium> pNewParent;
690 for (size_t a = mtc.chain.size(); a > 0; --a)
691 {
692 const MEDIUMTASK &mt = mtc.chain.at(a - 1);
693 ComPtr<IMedium> pMedium = mt.pMedium;
694
695 Bstr bstrSrcName;
696 rc = pMedium->COMGETTER(Name)(bstrSrcName.asOutParam());
697 if (FAILED(rc)) throw rc;
698
699 rc = d->pProgress->SetNextOperation(BstrFmt(p->tr("Cloning Disk '%ls' ..."), bstrSrcName.raw()).raw(), mt.uWeight);
700 if (FAILED(rc)) throw rc;
701
702 Bstr bstrSrcId;
703 rc = pMedium->COMGETTER(Id)(bstrSrcId.asOutParam());
704 if (FAILED(rc)) throw rc;
705
706 if (mtc.fAttachLinked)
707 {
708 IMedium *pTmp = pMedium;
709 ComObjPtr<Medium> pLMedium = static_cast<Medium*>(pTmp);
710 if (pLMedium.isNull())
711 throw E_POINTER;
712 ComObjPtr<Medium> pBase = pLMedium->getBase();
713 if (pBase->isReadOnly())
714 {
715 ComObjPtr<Medium> pDiff;
716 /* create the diff under the snapshot medium */
717 rc = createDiffHelper(pLMedium, strTrgSnapshotFolder,
718 &newMedia, &pDiff);
719 if (FAILED(rc)) throw rc;
720 map.insert(TStrMediumPair(Utf8Str(bstrSrcId), pDiff));
721 /* diff image has to be used... */
722 pNewParent = pDiff;
723 }
724 else
725 {
726 /* Attach the medium directly, as its type is not
727 * subject to diff creation. */
728 newMedia.append(pLMedium);
729 map.insert(TStrMediumPair(Utf8Str(bstrSrcId), pLMedium));
730 pNewParent = pLMedium;
731 }
732 }
733 else
734 {
735 /* Is a clone already there? */
736 TStrMediumMap::iterator it = map.find(Utf8Str(bstrSrcId));
737 if (it != map.end())
738 pNewParent = it->second;
739 else
740 {
741 ComPtr<IMediumFormat> pSrcFormat;
742 rc = pMedium->COMGETTER(MediumFormat)(pSrcFormat.asOutParam());
743 ULONG uSrcCaps = 0;
744 rc = pSrcFormat->COMGETTER(Capabilities)(&uSrcCaps);
745 if (FAILED(rc)) throw rc;
746
747 /* Default format? */
748 Utf8Str strDefaultFormat;
749 p->mParent->getDefaultHardDiskFormat(strDefaultFormat);
750 Bstr bstrSrcFormat(strDefaultFormat);
751 ULONG srcVar = MediumVariant_Standard;
752 /* Is the source file based? */
753 if ((uSrcCaps & MediumFormatCapabilities_File) == MediumFormatCapabilities_File)
754 {
755 /* Yes, just use the source format. Otherwise the defaults
756 * will be used. */
757 rc = pMedium->COMGETTER(Format)(bstrSrcFormat.asOutParam());
758 if (FAILED(rc)) throw rc;
759 rc = pMedium->COMGETTER(Variant)(&srcVar);
760 if (FAILED(rc)) throw rc;
761 }
762
763 Guid newId;
764 newId.create();
765 Utf8Str strNewName(bstrSrcName);
766 if (!fKeepDiskNames)
767 {
768 Utf8Str strSrcTest = bstrSrcName;
769 /* Check if we have to use another name. */
770 if (!mt.strBaseName.isEmpty())
771 strSrcTest = mt.strBaseName;
772 strSrcTest.stripExt();
773 /* If the old disk name was in {uuid} format we also
774 * want the new name in this format, but with the
775 * updated id of course. If the old disk was called
776 * like the VM name, we change it to the new VM name.
777 * For all other disks we rename them with this
778 * template: "new name-disk1.vdi". */
779 if (strSrcTest == strOldVMName)
780 strNewName = Utf8StrFmt("%s%s", trgMCF.machineUserData.strName.c_str(), RTPathExt(Utf8Str(bstrSrcName).c_str()));
781 else if ( strSrcTest.startsWith("{")
782 && strSrcTest.endsWith("}"))
783 {
784 strSrcTest = strSrcTest.substr(1, strSrcTest.length() - 2);
785 if (isValidGuid(strSrcTest))
786 strNewName = Utf8StrFmt("%s%s", newId.toStringCurly().c_str(), RTPathExt(strNewName.c_str()));
787 }
788 else
789 strNewName = Utf8StrFmt("%s-disk%d%s", trgMCF.machineUserData.strName.c_str(), ++cDisks, RTPathExt(Utf8Str(bstrSrcName).c_str()));
790 }
791
792 /* Check if this medium comes from the snapshot folder, if
793 * so, put it there in the cloned machine as well.
794 * Otherwise it goes to the machine folder. */
795 Bstr bstrSrcPath;
796 Utf8Str strFile = Utf8StrFmt("%s%c%s", strTrgMachineFolder.c_str(), RTPATH_DELIMITER, strNewName.c_str());
797 rc = pMedium->COMGETTER(Location)(bstrSrcPath.asOutParam());
798 if (FAILED(rc)) throw rc;
799 if ( !bstrSrcPath.isEmpty()
800 && RTPathStartsWith(Utf8Str(bstrSrcPath).c_str(), Utf8Str(bstrSrcSnapshotFolder).c_str())
801 && (fKeepDiskNames || mt.strBaseName.isEmpty()))
802 strFile = Utf8StrFmt("%s%c%s", strTrgSnapshotFolder.c_str(), RTPATH_DELIMITER, strNewName.c_str());
803
804 /* Start creating the clone. */
805 ComObjPtr<Medium> pTarget;
806 rc = pTarget.createObject();
807 if (FAILED(rc)) throw rc;
808
809 rc = pTarget->init(p->mParent,
810 Utf8Str(bstrSrcFormat),
811 strFile,
812 Guid::Empty, /* empty media registry */
813 NULL /* llRegistriesThatNeedSaving */);
814 if (FAILED(rc)) throw rc;
815
816 /* Update the new uuid. */
817 pTarget->updateId(newId);
818
819 srcLock.release();
820 /* Do the disk cloning. */
821 ComPtr<IProgress> progress2;
822 rc = pMedium->CloneTo(pTarget,
823 srcVar,
824 pNewParent,
825 progress2.asOutParam());
826 if (FAILED(rc)) throw rc;
827
828 /* Wait until the async process has finished. */
829 rc = d->pProgress->WaitForAsyncProgressCompletion(progress2);
830 srcLock.acquire();
831 if (FAILED(rc)) throw rc;
832
833 /* Check the result of the async process. */
834 LONG iRc;
835 rc = progress2->COMGETTER(ResultCode)(&iRc);
836 if (FAILED(rc)) throw rc;
837 if (FAILED(iRc))
838 {
839 /* If the thread of the progress object has an error, then
840 * retrieve the error info from there, or it'll be lost. */
841 ProgressErrorInfo info(progress2);
842 throw p->setError(iRc, Utf8Str(info.getText()).c_str());
843 }
844 /* Remember created medium. */
845 newMedia.append(pTarget);
846 /* Get the medium type from the source and set it to the
847 * new medium. */
848 MediumType_T type;
849 rc = pMedium->COMGETTER(Type)(&type);
850 if (FAILED(rc)) throw rc;
851 rc = pTarget->COMSETTER(Type)(type);
852 if (FAILED(rc)) throw rc;
853 map.insert(TStrMediumPair(Utf8Str(bstrSrcId), pTarget));
854 /* register the new harddisk */
855 {
856 AutoWriteLock tlock(p->mParent->getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
857 rc = p->mParent->registerHardDisk(pTarget, NULL /* pllRegistriesThatNeedSaving */);
858 if (FAILED(rc)) throw rc;
859 }
860 /* This medium becomes the parent of the next medium in the
861 * chain. */
862 pNewParent = pTarget;
863 }
864 }
865 }
866
867 Bstr bstrSrcId;
868 rc = mtc.chain.first().pMedium->COMGETTER(Id)(bstrSrcId.asOutParam());
869 if (FAILED(rc)) throw rc;
870 Bstr bstrTrgId;
871 rc = pNewParent->COMGETTER(Id)(bstrTrgId.asOutParam());
872 if (FAILED(rc)) throw rc;
873 /* update snapshot configuration */
874 d->updateSnapshotStorageLists(trgMCF.llFirstSnapshot, bstrSrcId, bstrTrgId);
875
876 /* create new 'Current State' diff for caller defined place */
877 if (mtc.fCreateDiffs)
878 {
879 const MEDIUMTASK &mt = mtc.chain.first();
880 ComObjPtr<Medium> pLMedium = static_cast<Medium*>((IMedium*)mt.pMedium);
881 if (pLMedium.isNull())
882 throw E_POINTER;
883 ComObjPtr<Medium> pBase = pLMedium->getBase();
884 if (pBase->isReadOnly())
885 {
886 ComObjPtr<Medium> pDiff;
887 rc = createDiffHelper(pNewParent, strTrgSnapshotFolder,
888 &newMedia, &pDiff);
889 if (FAILED(rc)) throw rc;
890 /* diff image has to be used... */
891 pNewParent = pDiff;
892 }
893 else
894 {
895 /* Attach the medium directly, as its type is not
896 * subject to diff creation. */
897 newMedia.append(pNewParent);
898 }
899
900 rc = pNewParent->COMGETTER(Id)(bstrTrgId.asOutParam());
901 if (FAILED(rc)) throw rc;
902 }
903 /* update 'Current State' configuration */
904 d->updateStorageLists(trgMCF.storageMachine.llStorageControllers, bstrSrcId, bstrTrgId);
905 }
906 /* Make sure all disks know of the new machine uuid. We do this last to
907 * be able to change the medium type above. */
908 for (size_t i = newMedia.size(); i > 0; --i)
909 {
910 const ComObjPtr<Medium> &pMedium = newMedia.at(i - 1);
911 AutoCaller mac(pMedium);
912 if (FAILED(mac.rc())) throw mac.rc();
913 AutoWriteLock mlock(pMedium COMMA_LOCKVAL_SRC_POS);
914 Guid uuid = d->pTrgMachine->mData->mUuid;
915 if (d->options.contains(CloneOptions_Link))
916 {
917 ComObjPtr<Medium> pParent = pMedium->getParent();
918 mlock.release();
919 if (!pParent.isNull())
920 {
921 AutoCaller mac2(pParent);
922 if (FAILED(mac2.rc())) throw mac2.rc();
923 AutoReadLock mlock2(pParent COMMA_LOCKVAL_SRC_POS);
924 if (pParent->getFirstRegistryMachineId(uuid))
925 VirtualBox::addGuidToListUniquely(llRegistriesThatNeedSaving, uuid);
926 }
927 mlock.acquire();
928 }
929 pMedium->addRegistry(uuid, false /* fRecurse */);
930 }
931 /* Check if a snapshot folder is necessary and if so doesn't already
932 * exists. */
933 if ( !d->llSaveStateFiles.isEmpty()
934 && !RTDirExists(strTrgSnapshotFolder.c_str()))
935 {
936 int vrc = RTDirCreateFullPath(strTrgSnapshotFolder.c_str(), 0777);
937 if (RT_FAILURE(vrc))
938 throw p->setError(VBOX_E_IPRT_ERROR,
939 p->tr("Could not create snapshots folder '%s' (%Rrc)"), strTrgSnapshotFolder.c_str(), vrc);
940 }
941 /* Clone all save state files. */
942 for (size_t i = 0; i < d->llSaveStateFiles.size(); ++i)
943 {
944 SAVESTATETASK sst = d->llSaveStateFiles.at(i);
945 const Utf8Str &strTrgSaveState = Utf8StrFmt("%s%c%s", strTrgSnapshotFolder.c_str(), RTPATH_DELIMITER, RTPathFilename(sst.strSaveStateFile.c_str()));
946
947 /* Move to next sub-operation. */
948 rc = d->pProgress->SetNextOperation(BstrFmt(p->tr("Copy save state file '%s' ..."), RTPathFilename(sst.strSaveStateFile.c_str())).raw(), sst.uWeight);
949 if (FAILED(rc)) throw rc;
950 /* Copy the file only if it was not copied already. */
951 if (!newFiles.contains(strTrgSaveState.c_str()))
952 {
953 int vrc = RTFileCopyEx(sst.strSaveStateFile.c_str(), strTrgSaveState.c_str(), 0, MachineCloneVMPrivate::copyStateFileProgress, &d->pProgress);
954 if (RT_FAILURE(vrc))
955 throw p->setError(VBOX_E_IPRT_ERROR,
956 p->tr("Could not copy state file '%s' to '%s' (%Rrc)"), sst.strSaveStateFile.c_str(), strTrgSaveState.c_str(), vrc);
957 newFiles.append(strTrgSaveState);
958 }
959 /* Update the path in the configuration either for the current
960 * machine state or the snapshots. */
961 if (sst.snapshotUuid.isEmpty())
962 trgMCF.strStateFile = strTrgSaveState;
963 else
964 d->updateStateFile(trgMCF.llFirstSnapshot, sst.snapshotUuid, strTrgSaveState);
965 }
966
967 {
968 rc = d->pProgress->SetNextOperation(BstrFmt(p->tr("Create Machine Clone '%s' ..."), trgMCF.machineUserData.strName.c_str()).raw(), 1);
969 if (FAILED(rc)) throw rc;
970 /* After modifying the new machine config, we can copy the stuff
971 * over to the new machine. The machine have to be mutable for
972 * this. */
973 rc = d->pTrgMachine->checkStateDependency(p->MutableStateDep);
974 if (FAILED(rc)) throw rc;
975 rc = d->pTrgMachine->loadMachineDataFromSettings(trgMCF,
976 &d->pTrgMachine->mData->mUuid);
977 if (FAILED(rc)) throw rc;
978 }
979
980 /* Now save the new configuration to disk. */
981 rc = d->pTrgMachine->SaveSettings();
982 if (FAILED(rc)) throw rc;
983 trgLock.release();
984 if (!llRegistriesThatNeedSaving.empty())
985 {
986 srcLock.release();
987 rc = p->mParent->saveRegistries(llRegistriesThatNeedSaving);
988 if (FAILED(rc)) throw rc;
989 }
990 }
991 catch (HRESULT rc2)
992 {
993 rc = rc2;
994 }
995 catch (...)
996 {
997 rc = VirtualBox::handleUnexpectedExceptions(RT_SRC_POS);
998 }
999
1000 MultiResult mrc(rc);
1001 /* Cleanup on failure (CANCEL also) */
1002 if (FAILED(rc))
1003 {
1004 int vrc = VINF_SUCCESS;
1005 /* Delete all created files. */
1006 for (size_t i = 0; i < newFiles.size(); ++i)
1007 {
1008 vrc = RTFileDelete(newFiles.at(i).c_str());
1009 if (RT_FAILURE(vrc))
1010 mrc = p->setError(VBOX_E_IPRT_ERROR, p->tr("Could not delete file '%s' (%Rrc)"), newFiles.at(i).c_str(), vrc);
1011 }
1012 /* Delete all already created medias. (Reverse, cause there could be
1013 * parent->child relations.) */
1014 for (size_t i = newMedia.size(); i > 0; --i)
1015 {
1016 const ComObjPtr<Medium> &pMedium = newMedia.at(i - 1);
1017 mrc = pMedium->deleteStorage(NULL /* aProgress */,
1018 true /* aWait */,
1019 NULL /* llRegistriesThatNeedSaving */);
1020 pMedium->Close();
1021 }
1022 /* Delete the snapshot folder when not empty. */
1023 if (!strTrgSnapshotFolder.isEmpty())
1024 RTDirRemove(strTrgSnapshotFolder.c_str());
1025 /* Delete the machine folder when not empty. */
1026 RTDirRemove(strTrgMachineFolder.c_str());
1027 }
1028
1029 return mrc;
1030}
1031
1032HRESULT MachineCloneVM::createDiffHelper(const ComObjPtr<Medium> &pParent,
1033 const Utf8Str &strSnapshotFolder,
1034 RTCList< ComObjPtr<Medium> > *pNewMedia,
1035 ComObjPtr<Medium> *ppDiff)
1036{
1037 DPTR(MachineCloneVM);
1038 ComObjPtr<Machine> &p = d->p;
1039 HRESULT rc = S_OK;
1040
1041 try
1042 {
1043 Bstr bstrSrcId;
1044 rc = pParent->COMGETTER(Id)(bstrSrcId.asOutParam());
1045 if (FAILED(rc)) throw rc;
1046 ComObjPtr<Medium> diff;
1047 diff.createObject();
1048 rc = diff->init(p->mParent,
1049 pParent->getPreferredDiffFormat(),
1050 Utf8StrFmt("%s%c", strSnapshotFolder.c_str(), RTPATH_DELIMITER),
1051 Guid::Empty, /* empty media registry */
1052 NULL); /* pllRegistriesThatNeedSaving */
1053 if (FAILED(rc)) throw rc;
1054 MediumLockList *pMediumLockList(new MediumLockList());
1055 rc = diff->createMediumLockList(true /* fFailIfInaccessible */,
1056 true /* fMediumLockWrite */,
1057 pParent,
1058 *pMediumLockList);
1059 if (FAILED(rc)) throw rc;
1060 rc = pMediumLockList->Lock();
1061 if (FAILED(rc)) throw rc;
1062 /* this already registers the new diff image */
1063 rc = pParent->createDiffStorage(diff, MediumVariant_Standard,
1064 pMediumLockList,
1065 NULL /* aProgress */,
1066 true /* aWait */,
1067 NULL); // pllRegistriesThatNeedSaving
1068 delete pMediumLockList;
1069 if (FAILED(rc)) throw rc;
1070 /* Remember created medium. */
1071 pNewMedia->append(diff);
1072 *ppDiff = diff;
1073 }
1074 catch (HRESULT rc2)
1075 {
1076 rc = rc2;
1077 }
1078 catch (...)
1079 {
1080 rc = VirtualBox::handleUnexpectedExceptions(RT_SRC_POS);
1081 }
1082
1083 return rc;
1084}
1085
1086void MachineCloneVM::destroy()
1087{
1088 delete this;
1089}
1090
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