VirtualBox

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

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

Main/Machine: implement creating linked clones.
Frontends/VBoxManage: enable creating linked clones

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