VirtualBox

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

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

Main-CloneVM: needs tree lock

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 36.9 KB
Line 
1/* $Id: MachineImplCloneVM.cpp 37604 2011-06-23 08:38:23Z 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 ULONG uWeight;
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 ULONG uWeight;
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 their 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.uWeight = (lSize + _1M - 1) / _1M;
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, i.e. the weight is the sum of the data which
385 * needs to be read and written. */
386 uint64_t uMaxSize = 0;
387 for (size_t e = mtc.chain.size(); e > 0; --e)
388 {
389 MEDIUMTASK &mt = mtc.chain.at(e - 1);
390 mt.uWeight += uMaxSize;
391
392 /* Calculate progress data */
393 ++uCount;
394 uTotalWeight += mt.uWeight;
395
396 /* Save the max size for better weighting of diff image
397 * creation. */
398 uMaxSize = RT_MAX(uMaxSize, mt.uWeight);
399 }
400 d->llMedias.append(mtc);
401 }
402 Bstr bstrSrcSaveStatePath;
403 rc = machine->COMGETTER(StateFilePath)(bstrSrcSaveStatePath.asOutParam());
404 if (FAILED(rc)) throw rc;
405 if (!bstrSrcSaveStatePath.isEmpty())
406 {
407 SAVESTATETASK sst;
408 sst.snapshotUuid = machine->getSnapshotId();
409 sst.strSaveStateFile = bstrSrcSaveStatePath;
410 uint64_t cbSize;
411 int vrc = RTFileQuerySize(sst.strSaveStateFile.c_str(), &cbSize);
412 if (RT_FAILURE(vrc))
413 throw p->setError(VBOX_E_IPRT_ERROR, p->tr("Could not query file size of '%s' (%Rrc)"), sst.strSaveStateFile.c_str(), vrc);
414 /* same rule as above: count both the data which needs to
415 * be read and written */
416 sst.uWeight = 2 * (cbSize + _1M - 1) / _1M;
417 d->llSaveStateFiles.append(sst);
418 ++uCount;
419 uTotalWeight += sst.uWeight;
420 }
421 }
422
423 rc = d->pProgress.createObject();
424 if (FAILED(rc)) throw rc;
425 rc = d->pProgress->init(p->getVirtualBox(),
426 static_cast<IMachine*>(d->pSrcMachine) /* aInitiator */,
427 Bstr(p->tr("Cloning Machine")).raw(),
428 true /* fCancellable */,
429 uCount,
430 uTotalWeight,
431 Bstr(p->tr("Initialize Cloning")).raw(),
432 1);
433 if (FAILED(rc)) throw rc;
434
435 int vrc = d->startWorker();
436
437 if (RT_FAILURE(vrc))
438 p->setError(VBOX_E_IPRT_ERROR, "Could not create machine clone thread (%Rrc)", vrc);
439 }
440 catch (HRESULT rc2)
441 {
442 rc = rc2;
443 }
444
445 if (SUCCEEDED(rc))
446 d->pProgress.queryInterfaceTo(pProgress);
447
448 return rc;
449}
450
451HRESULT MachineCloneVM::run()
452{
453 DPTR(MachineCloneVM);
454 ComObjPtr<Machine> &p = d->p;
455
456 AutoCaller autoCaller(p);
457 if (FAILED(autoCaller.rc())) return autoCaller.rc();
458
459 AutoReadLock srcLock(p COMMA_LOCKVAL_SRC_POS);
460 AutoWriteLock trgLock(d->pTrgMachine COMMA_LOCKVAL_SRC_POS);
461
462 HRESULT rc = S_OK;
463
464 /*
465 * Todo:
466 * - Snapshot diffs (can) have the uuid as name. After cloning this isn't
467 * right anymore. Is it worth to change to the new uuid? Or should the
468 * cloned disks called exactly as the original one or should all new disks
469 * get a new name with the new VM name in it.
470 * - What about log files?
471 */
472
473 /* Where should all the media go? */
474 Utf8Str strTrgSnapshotFolder;
475 Utf8Str strTrgMachineFolder = d->pTrgMachine->getSettingsFileFull();
476 strTrgMachineFolder.stripFilename();
477
478 RTCList< ComObjPtr<Medium> > newMedias; /* All created images */
479 RTCList<Utf8Str> newFiles; /* All extra created files (save states, ...) */
480 try
481 {
482 /* Copy all the configuration from this machine to an empty
483 * configuration dataset. */
484 settings::MachineConfigFile trgMCF = *d->pSrcMachine->mData->pMachineConfigFile;
485
486 /* Reset media registry. */
487 trgMCF.mediaRegistry.llHardDisks.clear();
488 /* If we got a valid snapshot id, replace the hardware/storage section
489 * with the stuff from the snapshot. */
490 settings::Snapshot sn;
491 if (!d->snapshotId.isEmpty())
492 sn = d->findSnapshot(&trgMCF, trgMCF.llFirstSnapshot, d->snapshotId);
493
494 if (d->mode == CloneMode_MachineState)
495 {
496 if (!sn.uuid.isEmpty())
497 {
498 trgMCF.hardwareMachine = sn.hardware;
499 trgMCF.storageMachine = sn.storage;
500 }
501
502 /* Remove any hint on snapshots. */
503 trgMCF.llFirstSnapshot.clear();
504 trgMCF.uuidCurrentSnapshot.clear();
505 }else
506 if ( d->mode == CloneMode_MachineAndChildStates
507 && !sn.uuid.isEmpty())
508 {
509 /* Copy the snapshot data to the current machine. */
510 trgMCF.hardwareMachine = sn.hardware;
511 trgMCF.storageMachine = sn.storage;
512
513 /* The snapshot will be the root one. */
514 trgMCF.uuidCurrentSnapshot = sn.uuid;
515 trgMCF.llFirstSnapshot.clear();
516 trgMCF.llFirstSnapshot.push_back(sn);
517 }
518
519 /* Generate new MAC addresses for all machines when not forbidden. */
520 if (!d->options.contains(CloneOptions_KeepAllMACs))
521 {
522 d->updateMACAddresses(trgMCF.hardwareMachine.llNetworkAdapters);
523 d->updateMACAddresses(trgMCF.llFirstSnapshot);
524 }
525
526 /* When the current snapshot folder is absolute we reset it to the
527 * default relative folder. */
528 if (RTPathStartsWithRoot(trgMCF.machineUserData.strSnapshotFolder.c_str()))
529 trgMCF.machineUserData.strSnapshotFolder = "Snapshots";
530 trgMCF.strStateFile = "";
531 /* Force writing of setting file. */
532 trgMCF.fCurrentStateModified = true;
533 /* Set the new name. */
534 trgMCF.machineUserData.strName = d->pTrgMachine->mUserData->s.strName;
535 trgMCF.uuid = d->pTrgMachine->mData->mUuid;
536
537 Bstr bstrSrcSnapshotFolder;
538 rc = d->pSrcMachine->COMGETTER(SnapshotFolder)(bstrSrcSnapshotFolder.asOutParam());
539 if (FAILED(rc)) throw rc;
540 /* The absolute name of the snapshot folder. */
541 strTrgSnapshotFolder = Utf8StrFmt("%s%c%s", strTrgMachineFolder.c_str(), RTPATH_DELIMITER, trgMCF.machineUserData.strSnapshotFolder.c_str());
542
543 /* We need to create a map with the already created medias. This is
544 * necessary, cause different snapshots could have the same
545 * parents/parent chain. If a medium is in this map already, it isn't
546 * cloned a second time, but simply used. */
547 typedef std::map<Utf8Str, ComObjPtr<Medium> > TStrMediumMap;
548 typedef std::pair<Utf8Str, ComObjPtr<Medium> > TStrMediumPair;
549 TStrMediumMap map;
550 for (size_t i = 0; i < d->llMedias.size(); ++i)
551 {
552 const MEDIUMTASKCHAIN &mtc = d->llMedias.at(i);
553 ComObjPtr<Medium> pNewParent;
554 for (size_t a = mtc.chain.size(); a > 0; --a)
555 {
556 const MEDIUMTASK &mt = mtc.chain.at(a - 1);
557 ComPtr<IMedium> pMedium = mt.pMedium;
558
559 Bstr bstrSrcName;
560 rc = pMedium->COMGETTER(Name)(bstrSrcName.asOutParam());
561 if (FAILED(rc)) throw rc;
562
563 rc = d->pProgress->SetNextOperation(BstrFmt(p->tr("Cloning Disk '%ls' ..."), bstrSrcName.raw()).raw(), mt.uWeight);
564 if (FAILED(rc)) throw rc;
565
566 Bstr bstrSrcId;
567 rc = pMedium->COMGETTER(Id)(bstrSrcId.asOutParam());
568 if (FAILED(rc)) throw rc;
569
570 /* Is a clone already there? */
571 TStrMediumMap::iterator it = map.find(Utf8Str(bstrSrcId));
572 if (it != map.end())
573 pNewParent = it->second;
574 else
575 {
576 ComPtr<IMediumFormat> pSrcFormat;
577 rc = pMedium->COMGETTER(MediumFormat)(pSrcFormat.asOutParam());
578 ULONG uSrcCaps = 0;
579 rc = pSrcFormat->COMGETTER(Capabilities)(&uSrcCaps);
580 if (FAILED(rc)) throw rc;
581
582 /* Default format? */
583 Utf8Str strDefaultFormat;
584 p->mParent->getDefaultHardDiskFormat(strDefaultFormat);
585 Bstr bstrSrcFormat(strDefaultFormat);
586 ULONG srcVar = MediumVariant_Standard;
587 /* Is the source file based? */
588 if ((uSrcCaps & MediumFormatCapabilities_File) == MediumFormatCapabilities_File)
589 {
590 /* Yes, just use the source format. Otherwise the defaults
591 * will be used. */
592 rc = pMedium->COMGETTER(Format)(bstrSrcFormat.asOutParam());
593 if (FAILED(rc)) throw rc;
594 rc = pMedium->COMGETTER(Variant)(&srcVar);
595 if (FAILED(rc)) throw rc;
596 }
597
598 /* Check if this medium comes from the snapshot folder, if
599 * so, put it there in the cloned machine as well.
600 * Otherwise it goes to the machine folder. */
601 Utf8Str strFile = Utf8StrFmt("%s%c%lS", strTrgMachineFolder.c_str(), RTPATH_DELIMITER, bstrSrcName.raw());
602 Bstr bstrSrcPath;
603 rc = pMedium->COMGETTER(Location)(bstrSrcPath.asOutParam());
604 if (FAILED(rc)) throw rc;
605 if ( !bstrSrcPath.isEmpty()
606 && RTPathStartsWith(Utf8Str(bstrSrcPath).c_str(), Utf8Str(bstrSrcSnapshotFolder).c_str()))
607 strFile = Utf8StrFmt("%s%c%lS", strTrgSnapshotFolder.c_str(), RTPATH_DELIMITER, bstrSrcName.raw());
608 else
609 strFile = Utf8StrFmt("%s%c%lS", strTrgMachineFolder.c_str(), RTPATH_DELIMITER, bstrSrcName.raw());
610
611 /* Start creating the clone. */
612 ComObjPtr<Medium> pTarget;
613 rc = pTarget.createObject();
614 if (FAILED(rc)) throw rc;
615
616 rc = pTarget->init(p->mParent,
617 Utf8Str(bstrSrcFormat),
618 strFile,
619 Guid::Empty, /* empty media registry */
620 NULL /* llRegistriesThatNeedSaving */);
621 if (FAILED(rc)) throw rc;
622
623 srcLock.release();
624 /* Do the disk cloning. */
625 ComPtr<IProgress> progress2;
626 rc = pMedium->CloneTo(pTarget,
627 srcVar,
628 pNewParent,
629 progress2.asOutParam());
630 if (FAILED(rc)) throw rc;
631
632 /* Wait until the asynchrony process has finished. */
633 rc = d->pProgress->WaitForAsyncProgressCompletion(progress2);
634 srcLock.acquire();
635 if (FAILED(rc)) throw rc;
636
637 /* Check the result of the asynchrony process. */
638 LONG iRc;
639 rc = progress2->COMGETTER(ResultCode)(&iRc);
640 if (FAILED(rc)) throw rc;
641 if (FAILED(iRc))
642 {
643 /* If the thread of the progress object has an error, then
644 * retrieve the error info from there, or it'll be lost. */
645 ProgressErrorInfo info(progress2);
646 throw p->setError(iRc, Utf8Str(info.getText()).c_str());
647 }
648 /* Remember created medias. */
649 newMedias.append(pTarget);
650 /* Get the medium type from the source and set it to the
651 * new medium. */
652 MediumType_T type;
653 rc = pMedium->COMGETTER(Type)(&type);
654 if (FAILED(rc)) throw rc;
655 rc = pTarget->COMSETTER(Type)(type);
656 if (FAILED(rc)) throw rc;
657 map.insert(TStrMediumPair(Utf8Str(bstrSrcId), pTarget));
658 /* Global register the new harddisk */
659 {
660 AutoWriteLock tlock(p->mParent->getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
661 rc = p->mParent->registerHardDisk(pTarget, NULL /* pllRegistriesThatNeedSaving */);
662 if (FAILED(rc)) return rc;
663 }
664 /* This medium becomes the parent of the next medium in the
665 * chain. */
666 pNewParent = pTarget;
667 }
668 }
669
670 /* Create diffs for the last image chain. */
671 if (mtc.fCreateDiffs)
672 {
673 Bstr bstrSrcId;
674 rc = pNewParent->COMGETTER(Id)(bstrSrcId.asOutParam());
675 if (FAILED(rc)) throw rc;
676 ComObjPtr<Medium> diff;
677 diff.createObject();
678 rc = diff->init(p->mParent,
679 pNewParent->getPreferredDiffFormat(),
680 Utf8StrFmt("%s%c", strTrgSnapshotFolder.c_str(), RTPATH_DELIMITER),
681 Guid::Empty, /* empty media registry */
682 NULL); /* pllRegistriesThatNeedSaving */
683 if (FAILED(rc)) throw rc;
684 MediumLockList *pMediumLockList(new MediumLockList());
685 rc = diff->createMediumLockList(true /* fFailIfInaccessible */,
686 true /* fMediumLockWrite */,
687 pNewParent,
688 *pMediumLockList);
689 if (FAILED(rc)) throw rc;
690 rc = pMediumLockList->Lock();
691 if (FAILED(rc)) throw rc;
692 rc = pNewParent->createDiffStorage(diff, MediumVariant_Standard,
693 pMediumLockList,
694 NULL /* aProgress */,
695 true /* aWait */,
696 NULL); // pllRegistriesThatNeedSaving
697 delete pMediumLockList;
698 if (FAILED(rc)) throw rc;
699 /* Remember created medias. */
700 newMedias.append(diff);
701 /* Global register the new harddisk */
702 {
703 AutoWriteLock tlock(p->mParent->getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
704 rc = p->mParent->registerHardDisk(diff, NULL /* pllRegistriesThatNeedSaving */);
705 if (FAILED(rc)) return rc;
706 }
707 /* This medium becomes the parent of the next medium in the
708 * chain. */
709 pNewParent = diff;
710 }
711 Bstr bstrSrcId;
712 rc = mtc.chain.first().pMedium->COMGETTER(Id)(bstrSrcId.asOutParam());
713 if (FAILED(rc)) throw rc;
714 Bstr bstrTrgId;
715 rc = pNewParent->COMGETTER(Id)(bstrTrgId.asOutParam());
716 if (FAILED(rc)) throw rc;
717 /* We have to patch the configuration, so it contains the new
718 * medium uuid instead of the old one. */
719 d->updateStorageLists(trgMCF.storageMachine.llStorageControllers, bstrSrcId, bstrTrgId);
720 d->updateSnapshotStorageLists(trgMCF.llFirstSnapshot, bstrSrcId, bstrTrgId);
721 /* Make sure all parent disks know of the new machine uuid. We do
722 * this last to be able to change the medium type above. */
723 rc = pNewParent->addRegistry(d->pTrgMachine->mData->mUuid, false /* fRecursive */);
724 if (FAILED(rc)) throw rc;
725 }
726 /* Check if a snapshot folder is necessary and if so doesn't already
727 * exists. */
728 if ( !d->llSaveStateFiles.isEmpty()
729 && !RTDirExists(strTrgSnapshotFolder.c_str()))
730 {
731 int vrc = RTDirCreateFullPath(strTrgSnapshotFolder.c_str(), 0777);
732 if (RT_FAILURE(vrc))
733 throw p->setError(VBOX_E_IPRT_ERROR,
734 p->tr("Could not create snapshots folder '%s' (%Rrc)"), strTrgSnapshotFolder.c_str(), vrc);
735 }
736 /* Clone all save state files. */
737 for (size_t i = 0; i < d->llSaveStateFiles.size(); ++i)
738 {
739 SAVESTATETASK sst = d->llSaveStateFiles.at(i);
740 const Utf8Str &strTrgSaveState = Utf8StrFmt("%s%c%s", strTrgSnapshotFolder.c_str(), RTPATH_DELIMITER, RTPathFilename(sst.strSaveStateFile.c_str()));
741
742 /* Move to next sub-operation. */
743 rc = d->pProgress->SetNextOperation(BstrFmt(p->tr("Copy save state file '%s' ..."), RTPathFilename(sst.strSaveStateFile.c_str())).raw(), sst.uWeight);
744 if (FAILED(rc)) throw rc;
745 /* Copy the file only if it was not copied already. */
746 if (!newFiles.contains(strTrgSaveState.c_str()))
747 {
748 int vrc = RTFileCopyEx(sst.strSaveStateFile.c_str(), strTrgSaveState.c_str(), 0, MachineCloneVMPrivate::copyStateFileProgress, &d->pProgress);
749 if (RT_FAILURE(vrc))
750 throw p->setError(VBOX_E_IPRT_ERROR,
751 p->tr("Could not copy state file '%s' to '%s' (%Rrc)"), sst.strSaveStateFile.c_str(), strTrgSaveState.c_str(), vrc);
752 newFiles.append(strTrgSaveState);
753 }
754 /* Update the path in the configuration either for the current
755 * machine state or the snapshots. */
756 if (sst.snapshotUuid.isEmpty())
757 trgMCF.strStateFile = strTrgSaveState;
758 else
759 d->updateStateFile(trgMCF.llFirstSnapshot, sst.snapshotUuid, strTrgSaveState);
760 }
761
762 if (false)
763// if (!d->pOldMachineState.isNull())
764 {
765 SafeIfaceArray<IMediumAttachment> sfaAttachments;
766 rc = d->pOldMachineState->COMGETTER(MediumAttachments)(ComSafeArrayAsOutParam(sfaAttachments));
767 if (FAILED(rc)) throw rc;
768 for (size_t a = 0; a < sfaAttachments.size(); ++a)
769 {
770 const ComPtr<IMediumAttachment> &pAtt = sfaAttachments[a];
771 DeviceType_T type;
772 rc = pAtt->COMGETTER(Type)(&type);
773 if (FAILED(rc)) throw rc;
774
775 /* Only harddisk's are of interest. */
776 if (type != DeviceType_HardDisk)
777 continue;
778
779 /* Valid medium attached? */
780 ComPtr<IMedium> pSrcMedium;
781 rc = pAtt->COMGETTER(Medium)(pSrcMedium.asOutParam());
782 if (FAILED(rc)) throw rc;
783 if (pSrcMedium.isNull())
784 continue;
785
786// ComObjPtr<Medium> pMedium = static_cast<Medium*>((IMedium*)pSrcMedium);
787// ComObjPtr<Medium> diff;
788// diff.createObject();
789 // store this diff in the same registry as the parent
790// Guid uuidRegistryParent;
791// if (!medium->getFirstRegistryMachineId(uuidRegistryParent))
792// {
793 // parent image has no registry: this can happen if we're attaching a new immutable
794 // image that has not yet been attached (medium then points to the base and we're
795 // creating the diff image for the immutable, and the parent is not yet registered);
796 // put the parent in the machine registry then
797// addMediumToRegistry(medium, llRegistriesThatNeedSaving, &uuidRegistryParent);
798// }
799// rc = diff->init(mParent,
800// pMedium->getPreferredDiffFormat(),
801// strFullSnapshotFolder.append(RTPATH_SLASH_STR),
802// uuidRegistryParent,
803// pllRegistriesThatNeedSaving);
804// if (FAILED(rc)) throw rc;
805//
806// rc = pMedium->createDiffStorage(diff, MediumVariant_Standard,
807// pMediumLockList,
808// NULL /* aProgress */,
809// true /* aWait */,
810// pllRegistriesThatNeedSaving);
811 }
812 }
813
814 {
815 rc = d->pProgress->SetNextOperation(BstrFmt(p->tr("Create Machine Clone '%s' ..."), trgMCF.machineUserData.strName.c_str()).raw(), 1);
816 if (FAILED(rc)) throw rc;
817 /* After modifying the new machine config, we can copy the stuff
818 * over to the new machine. The machine have to be mutable for
819 * this. */
820 rc = d->pTrgMachine->checkStateDependency(p->MutableStateDep);
821 if (FAILED(rc)) throw rc;
822 rc = d->pTrgMachine->loadMachineDataFromSettings(trgMCF,
823 &d->pTrgMachine->mData->mUuid);
824 if (FAILED(rc)) throw rc;
825 }
826
827 /* Now save the new configuration to disk. */
828 rc = d->pTrgMachine->SaveSettings();
829 if (FAILED(rc)) throw rc;
830 }
831 catch(HRESULT rc2)
832 {
833 rc = rc2;
834 }
835 catch (...)
836 {
837 rc = VirtualBox::handleUnexpectedExceptions(RT_SRC_POS);
838 }
839
840 MultiResult mrc(rc);
841 /* Cleanup on failure (CANCEL also) */
842 if (FAILED(rc))
843 {
844 int vrc = VINF_SUCCESS;
845 /* Delete all created files. */
846 for (size_t i = 0; i < newFiles.size(); ++i)
847 {
848 vrc = RTFileDelete(newFiles.at(i).c_str());
849 if (RT_FAILURE(vrc))
850 mrc = p->setError(VBOX_E_IPRT_ERROR, p->tr("Could not delete file '%s' (%Rrc)"), newFiles.at(i).c_str(), vrc);
851 }
852 /* Delete all already created medias. (Reverse, cause there could be
853 * parent->child relations.) */
854 for (size_t i = newMedias.size(); i > 0; --i)
855 {
856 bool fFile = false;
857 Utf8Str strLoc;
858 ComObjPtr<Medium> &pMedium = newMedias.at(i - 1);
859 {
860 AutoCaller mac(pMedium);
861 if (FAILED(mac.rc())) { continue; mrc = mac.rc(); }
862 AutoReadLock mlock(pMedium COMMA_LOCKVAL_SRC_POS);
863 fFile = pMedium->isMediumFormatFile();
864 strLoc = pMedium->getLocationFull();
865 }
866 if (fFile)
867 {
868 vrc = RTFileDelete(strLoc.c_str());
869 if (RT_FAILURE(vrc))
870 mrc = p->setError(VBOX_E_IPRT_ERROR, p->tr("Could not delete file '%s' (%Rrc)"), strLoc.c_str(), vrc);
871 }
872 }
873 /* Delete the snapshot folder when not empty. */
874 if (!strTrgSnapshotFolder.isEmpty())
875 RTDirRemove(strTrgSnapshotFolder.c_str());
876 /* Delete the machine folder when not empty. */
877 RTDirRemove(strTrgMachineFolder.c_str());
878 }
879
880 return mrc;
881}
882
883void MachineCloneVM::destroy()
884{
885 delete this;
886}
887
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