VirtualBox

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

Last change on this file since 37555 was 37555, checked in by vboxsync, 13 years ago

Main-CloneVM: better progress weighting for diff images

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

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