VirtualBox

source: vbox/trunk/src/VBox/Main/SnapshotImpl.cpp@ 29125

Last change on this file since 29125 was 29036, checked in by vboxsync, 15 years ago

Main/Snapshot: handle late failures more gracefully when the actual merging succeeded but something else failed. If the merge source is gone assume the merge worked and update the snapshot accordingly. Avoids inaccessible VM configs due to referring to an already gone image. Small additional cleanup with handling the flag whether VirtualBox.xml needs to be saved - do not depend on getting the "OR" done by the called method, do it explicitly.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 119.5 KB
Line 
1/* $Id: SnapshotImpl.cpp 29036 2010-05-04 14:54:12Z vboxsync $ */
2
3/** @file
4 *
5 * COM class implementation for Snapshot and SnapshotMachine in VBoxSVC.
6 */
7
8/*
9 * Copyright (C) 2006-2010 Oracle Corporation
10 *
11 * This file is part of VirtualBox Open Source Edition (OSE), as
12 * available from http://www.virtualbox.org. This file is free software;
13 * you can redistribute it and/or modify it under the terms of the GNU
14 * General Public License (GPL) as published by the Free Software
15 * Foundation, in version 2 as it comes in the "COPYING" file of the
16 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
17 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
18 */
19
20#include "Logging.h"
21#include "SnapshotImpl.h"
22
23#include "MachineImpl.h"
24#include "MediumImpl.h"
25#include "MediumFormatImpl.h"
26#include "Global.h"
27#include "ProgressImpl.h"
28
29// @todo these three includes are required for about one or two lines, try
30// to remove them and put that code in shared code in MachineImplcpp
31#include "SharedFolderImpl.h"
32#include "USBControllerImpl.h"
33#include "VirtualBoxImpl.h"
34
35#include "AutoCaller.h"
36
37#include <iprt/path.h>
38#include <VBox/param.h>
39#include <VBox/err.h>
40
41#include <VBox/settings.h>
42
43////////////////////////////////////////////////////////////////////////////////
44//
45// Globals
46//
47////////////////////////////////////////////////////////////////////////////////
48
49/**
50 * Progress callback handler for lengthy operations
51 * (corresponds to the FNRTPROGRESS typedef).
52 *
53 * @param uPercentage Completetion precentage (0-100).
54 * @param pvUser Pointer to the Progress instance.
55 */
56static DECLCALLBACK(int) progressCallback(unsigned uPercentage, void *pvUser)
57{
58 IProgress *progress = static_cast<IProgress*>(pvUser);
59
60 /* update the progress object */
61 if (progress)
62 progress->SetCurrentOperationProgress(uPercentage);
63
64 return VINF_SUCCESS;
65}
66
67////////////////////////////////////////////////////////////////////////////////
68//
69// Snapshot private data definition
70//
71////////////////////////////////////////////////////////////////////////////////
72
73typedef std::list< ComObjPtr<Snapshot> > SnapshotsList;
74
75struct Snapshot::Data
76{
77 Data()
78 : pVirtualBox(NULL)
79 {
80 RTTimeSpecSetMilli(&timeStamp, 0);
81 };
82
83 ~Data()
84 {}
85
86 const Guid uuid;
87 Utf8Str strName;
88 Utf8Str strDescription;
89 RTTIMESPEC timeStamp;
90 ComObjPtr<SnapshotMachine> pMachine;
91
92 /** weak VirtualBox parent */
93 VirtualBox * const pVirtualBox;
94
95 // pParent and llChildren are protected by the machine lock
96 ComObjPtr<Snapshot> pParent;
97 SnapshotsList llChildren;
98};
99
100////////////////////////////////////////////////////////////////////////////////
101//
102// Constructor / destructor
103//
104////////////////////////////////////////////////////////////////////////////////
105
106HRESULT Snapshot::FinalConstruct()
107{
108 LogFlowThisFunc(("\n"));
109 return S_OK;
110}
111
112void Snapshot::FinalRelease()
113{
114 LogFlowThisFunc(("\n"));
115 uninit();
116}
117
118/**
119 * Initializes the instance
120 *
121 * @param aId id of the snapshot
122 * @param aName name of the snapshot
123 * @param aDescription name of the snapshot (NULL if no description)
124 * @param aTimeStamp timestamp of the snapshot, in ms since 1970-01-01 UTC
125 * @param aMachine machine associated with this snapshot
126 * @param aParent parent snapshot (NULL if no parent)
127 */
128HRESULT Snapshot::init(VirtualBox *aVirtualBox,
129 const Guid &aId,
130 const Utf8Str &aName,
131 const Utf8Str &aDescription,
132 const RTTIMESPEC &aTimeStamp,
133 SnapshotMachine *aMachine,
134 Snapshot *aParent)
135{
136 LogFlowThisFunc(("uuid=%s aParent->uuid=%s\n", aId.toString().c_str(), (aParent) ? aParent->m->uuid.toString().c_str() : ""));
137
138 ComAssertRet(!aId.isEmpty() && !aName.isEmpty() && aMachine, E_INVALIDARG);
139
140 /* Enclose the state transition NotReady->InInit->Ready */
141 AutoInitSpan autoInitSpan(this);
142 AssertReturn(autoInitSpan.isOk(), E_FAIL);
143
144 m = new Data;
145
146 /* share parent weakly */
147 unconst(m->pVirtualBox) = aVirtualBox;
148
149 m->pParent = aParent;
150
151 unconst(m->uuid) = aId;
152 m->strName = aName;
153 m->strDescription = aDescription;
154 m->timeStamp = aTimeStamp;
155 m->pMachine = aMachine;
156
157 if (aParent)
158 aParent->m->llChildren.push_back(this);
159
160 /* Confirm a successful initialization when it's the case */
161 autoInitSpan.setSucceeded();
162
163 return S_OK;
164}
165
166/**
167 * Uninitializes the instance and sets the ready flag to FALSE.
168 * Called either from FinalRelease(), by the parent when it gets destroyed,
169 * or by a third party when it decides this object is no more valid.
170 *
171 * Since this manipulates the snapshots tree, the caller must hold the
172 * machine lock in write mode (which protects the snapshots tree)!
173 */
174void Snapshot::uninit()
175{
176 LogFlowThisFunc(("\n"));
177
178 /* Enclose the state transition Ready->InUninit->NotReady */
179 AutoUninitSpan autoUninitSpan(this);
180 if (autoUninitSpan.uninitDone())
181 return;
182
183 Assert(m->pMachine->isWriteLockOnCurrentThread());
184
185 // uninit all children
186 SnapshotsList::iterator it;
187 for (it = m->llChildren.begin();
188 it != m->llChildren.end();
189 ++it)
190 {
191 Snapshot *pChild = *it;
192 pChild->m->pParent.setNull();
193 pChild->uninit();
194 }
195 m->llChildren.clear(); // this unsets all the ComPtrs and probably calls delete
196
197 if (m->pParent)
198 deparent();
199
200 if (m->pMachine)
201 {
202 m->pMachine->uninit();
203 m->pMachine.setNull();
204 }
205
206 delete m;
207 m = NULL;
208}
209
210/**
211 * Delete the current snapshot by removing it from the tree of snapshots
212 * and reparenting its children.
213 *
214 * After this, the caller must call uninit() on the snapshot. We can't call
215 * that from here because if we do, the AutoUninitSpan waits forever for
216 * the number of callers to become 0 (it is 1 because of the AutoCaller in here).
217 *
218 * NOTE: this does NOT lock the snapshot, it is assumed that the machine state
219 * (and the snapshots tree) is protected by the caller having requested the machine
220 * lock in write mode AND the machine state must be DeletingSnapshot.
221 */
222void Snapshot::beginSnapshotDelete()
223{
224 AutoCaller autoCaller(this);
225 if (FAILED(autoCaller.rc()))
226 return;
227
228 // caller must have acquired the machine's write lock
229 Assert( m->pMachine->mData->mMachineState == MachineState_DeletingSnapshot
230 || m->pMachine->mData->mMachineState == MachineState_DeletingSnapshotOnline
231 || m->pMachine->mData->mMachineState == MachineState_DeletingSnapshotPaused);
232 Assert(m->pMachine->isWriteLockOnCurrentThread());
233
234 // the snapshot must have only one child when being deleted or no children at all
235 AssertReturnVoid(m->llChildren.size() <= 1);
236
237 ComObjPtr<Snapshot> parentSnapshot = m->pParent;
238
239 /// @todo (dmik):
240 // when we introduce clones later, deleting the snapshot will affect
241 // the current and first snapshots of clones, if they are direct children
242 // of this snapshot. So we will need to lock machines associated with
243 // child snapshots as well and update mCurrentSnapshot and/or
244 // mFirstSnapshot fields.
245
246 if (this == m->pMachine->mData->mCurrentSnapshot)
247 {
248 m->pMachine->mData->mCurrentSnapshot = parentSnapshot;
249
250 /* we've changed the base of the current state so mark it as
251 * modified as it no longer guaranteed to be its copy */
252 m->pMachine->mData->mCurrentStateModified = TRUE;
253 }
254
255 if (this == m->pMachine->mData->mFirstSnapshot)
256 {
257 if (m->llChildren.size() == 1)
258 {
259 ComObjPtr<Snapshot> childSnapshot = m->llChildren.front();
260 m->pMachine->mData->mFirstSnapshot = childSnapshot;
261 }
262 else
263 m->pMachine->mData->mFirstSnapshot.setNull();
264 }
265
266 // reparent our children
267 for (SnapshotsList::const_iterator it = m->llChildren.begin();
268 it != m->llChildren.end();
269 ++it)
270 {
271 ComObjPtr<Snapshot> child = *it;
272 // no need to lock, snapshots tree is protected by machine lock
273 child->m->pParent = m->pParent;
274 if (m->pParent)
275 m->pParent->m->llChildren.push_back(child);
276 }
277
278 // clear our own children list (since we reparented the children)
279 m->llChildren.clear();
280}
281
282/**
283 * Internal helper that removes "this" from the list of children of its
284 * parent. Used in uninit() and other places when reparenting is necessary.
285 *
286 * The caller must hold the machine lock in write mode (which protects the snapshots tree)!
287 */
288void Snapshot::deparent()
289{
290 Assert(m->pMachine->isWriteLockOnCurrentThread());
291
292 SnapshotsList &llParent = m->pParent->m->llChildren;
293 for (SnapshotsList::iterator it = llParent.begin();
294 it != llParent.end();
295 ++it)
296 {
297 Snapshot *pParentsChild = *it;
298 if (this == pParentsChild)
299 {
300 llParent.erase(it);
301 break;
302 }
303 }
304
305 m->pParent.setNull();
306}
307
308////////////////////////////////////////////////////////////////////////////////
309//
310// ISnapshot public methods
311//
312////////////////////////////////////////////////////////////////////////////////
313
314STDMETHODIMP Snapshot::COMGETTER(Id)(BSTR *aId)
315{
316 CheckComArgOutPointerValid(aId);
317
318 AutoCaller autoCaller(this);
319 if (FAILED(autoCaller.rc())) return autoCaller.rc();
320
321 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
322
323 m->uuid.toUtf16().cloneTo(aId);
324 return S_OK;
325}
326
327STDMETHODIMP Snapshot::COMGETTER(Name)(BSTR *aName)
328{
329 CheckComArgOutPointerValid(aName);
330
331 AutoCaller autoCaller(this);
332 if (FAILED(autoCaller.rc())) return autoCaller.rc();
333
334 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
335
336 m->strName.cloneTo(aName);
337 return S_OK;
338}
339
340/**
341 * @note Locks this object for writing, then calls Machine::onSnapshotChange()
342 * (see its lock requirements).
343 */
344STDMETHODIMP Snapshot::COMSETTER(Name)(IN_BSTR aName)
345{
346 CheckComArgStrNotEmptyOrNull(aName);
347
348 AutoCaller autoCaller(this);
349 if (FAILED(autoCaller.rc())) return autoCaller.rc();
350
351 Utf8Str strName(aName);
352
353 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
354
355 if (m->strName != strName)
356 {
357 m->strName = strName;
358
359 alock.leave(); /* Important! (child->parent locks are forbidden) */
360
361 // flag the machine as dirty or change won't get saved
362 AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS);
363 m->pMachine->setModified(Machine::IsModified_Snapshots);
364 mlock.leave();
365
366 return m->pMachine->onSnapshotChange(this);
367 }
368
369 return S_OK;
370}
371
372STDMETHODIMP Snapshot::COMGETTER(Description)(BSTR *aDescription)
373{
374 CheckComArgOutPointerValid(aDescription);
375
376 AutoCaller autoCaller(this);
377 if (FAILED(autoCaller.rc())) return autoCaller.rc();
378
379 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
380
381 m->strDescription.cloneTo(aDescription);
382 return S_OK;
383}
384
385STDMETHODIMP Snapshot::COMSETTER(Description)(IN_BSTR aDescription)
386{
387 AutoCaller autoCaller(this);
388 if (FAILED(autoCaller.rc())) return autoCaller.rc();
389
390 Utf8Str strDescription(aDescription);
391
392 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
393
394 if (m->strDescription != strDescription)
395 {
396 m->strDescription = strDescription;
397
398 alock.leave(); /* Important! (child->parent locks are forbidden) */
399
400 // flag the machine as dirty or change won't get saved
401 AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS);
402 m->pMachine->setModified(Machine::IsModified_Snapshots);
403 mlock.leave();
404
405 return m->pMachine->onSnapshotChange(this);
406 }
407
408 return S_OK;
409}
410
411STDMETHODIMP Snapshot::COMGETTER(TimeStamp)(LONG64 *aTimeStamp)
412{
413 CheckComArgOutPointerValid(aTimeStamp);
414
415 AutoCaller autoCaller(this);
416 if (FAILED(autoCaller.rc())) return autoCaller.rc();
417
418 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
419
420 *aTimeStamp = RTTimeSpecGetMilli(&m->timeStamp);
421 return S_OK;
422}
423
424STDMETHODIMP Snapshot::COMGETTER(Online)(BOOL *aOnline)
425{
426 CheckComArgOutPointerValid(aOnline);
427
428 AutoCaller autoCaller(this);
429 if (FAILED(autoCaller.rc())) return autoCaller.rc();
430
431 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
432
433 *aOnline = !stateFilePath().isEmpty();
434 return S_OK;
435}
436
437STDMETHODIMP Snapshot::COMGETTER(Machine)(IMachine **aMachine)
438{
439 CheckComArgOutPointerValid(aMachine);
440
441 AutoCaller autoCaller(this);
442 if (FAILED(autoCaller.rc())) return autoCaller.rc();
443
444 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
445
446 m->pMachine.queryInterfaceTo(aMachine);
447 return S_OK;
448}
449
450STDMETHODIMP Snapshot::COMGETTER(Parent)(ISnapshot **aParent)
451{
452 CheckComArgOutPointerValid(aParent);
453
454 AutoCaller autoCaller(this);
455 if (FAILED(autoCaller.rc())) return autoCaller.rc();
456
457 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
458
459 m->pParent.queryInterfaceTo(aParent);
460 return S_OK;
461}
462
463STDMETHODIMP Snapshot::COMGETTER(Children)(ComSafeArrayOut(ISnapshot *, aChildren))
464{
465 CheckComArgOutSafeArrayPointerValid(aChildren);
466
467 AutoCaller autoCaller(this);
468 if (FAILED(autoCaller.rc())) return autoCaller.rc();
469
470 // snapshots tree is protected by machine lock
471 AutoReadLock alock(m->pMachine COMMA_LOCKVAL_SRC_POS);
472
473 SafeIfaceArray<ISnapshot> collection(m->llChildren);
474 collection.detachTo(ComSafeArrayOutArg(aChildren));
475
476 return S_OK;
477}
478
479////////////////////////////////////////////////////////////////////////////////
480//
481// Snapshot public internal methods
482//
483////////////////////////////////////////////////////////////////////////////////
484
485/**
486 * Returns the parent snapshot or NULL if there's none. Must have caller + locking!
487 * @return
488 */
489const ComObjPtr<Snapshot>& Snapshot::getParent() const
490{
491 return m->pParent;
492}
493
494/**
495 * Returns the first child snapshot or NULL if there's none. Must have caller + locking!
496 * @return
497 */
498const ComObjPtr<Snapshot> Snapshot::getFirstChild() const
499{
500 if (!m->llChildren.size())
501 return NULL;
502 return m->llChildren.front();
503}
504
505/**
506 * @note
507 * Must be called from under the object's lock!
508 */
509const Utf8Str& Snapshot::stateFilePath() const
510{
511 return m->pMachine->mSSData->mStateFilePath;
512}
513
514/**
515 * @note
516 * Must be called from under the object's write lock!
517 */
518HRESULT Snapshot::deleteStateFile()
519{
520 int vrc = RTFileDelete(m->pMachine->mSSData->mStateFilePath.raw());
521 if (RT_SUCCESS(vrc))
522 m->pMachine->mSSData->mStateFilePath.setNull();
523 return RT_SUCCESS(vrc) ? S_OK : E_FAIL;
524}
525
526/**
527 * Returns the number of direct child snapshots, without grandchildren.
528 * Does not recurse.
529 * @return
530 */
531ULONG Snapshot::getChildrenCount()
532{
533 AutoCaller autoCaller(this);
534 AssertComRC(autoCaller.rc());
535
536 // snapshots tree is protected by machine lock
537 AutoReadLock alock(m->pMachine COMMA_LOCKVAL_SRC_POS);
538
539 return (ULONG)m->llChildren.size();
540}
541
542/**
543 * Implementation method for getAllChildrenCount() so we request the
544 * tree lock only once before recursing. Don't call directly.
545 * @return
546 */
547ULONG Snapshot::getAllChildrenCountImpl()
548{
549 AutoCaller autoCaller(this);
550 AssertComRC(autoCaller.rc());
551
552 ULONG count = (ULONG)m->llChildren.size();
553 for (SnapshotsList::const_iterator it = m->llChildren.begin();
554 it != m->llChildren.end();
555 ++it)
556 {
557 count += (*it)->getAllChildrenCountImpl();
558 }
559
560 return count;
561}
562
563/**
564 * Returns the number of child snapshots including all grandchildren.
565 * Recurses into the snapshots tree.
566 * @return
567 */
568ULONG Snapshot::getAllChildrenCount()
569{
570 AutoCaller autoCaller(this);
571 AssertComRC(autoCaller.rc());
572
573 // snapshots tree is protected by machine lock
574 AutoReadLock alock(m->pMachine COMMA_LOCKVAL_SRC_POS);
575
576 return getAllChildrenCountImpl();
577}
578
579/**
580 * Returns the SnapshotMachine that this snapshot belongs to.
581 * Caller must hold the snapshot's object lock!
582 * @return
583 */
584const ComObjPtr<SnapshotMachine>& Snapshot::getSnapshotMachine() const
585{
586 return m->pMachine;
587}
588
589/**
590 * Returns the UUID of this snapshot.
591 * Caller must hold the snapshot's object lock!
592 * @return
593 */
594Guid Snapshot::getId() const
595{
596 return m->uuid;
597}
598
599/**
600 * Returns the name of this snapshot.
601 * Caller must hold the snapshot's object lock!
602 * @return
603 */
604const Utf8Str& Snapshot::getName() const
605{
606 return m->strName;
607}
608
609/**
610 * Returns the time stamp of this snapshot.
611 * Caller must hold the snapshot's object lock!
612 * @return
613 */
614RTTIMESPEC Snapshot::getTimeStamp() const
615{
616 return m->timeStamp;
617}
618
619/**
620 * Searches for a snapshot with the given ID among children, grand-children,
621 * etc. of this snapshot. This snapshot itself is also included in the search.
622 *
623 * Caller must hold the machine lock (which protects the snapshots tree!)
624 */
625ComObjPtr<Snapshot> Snapshot::findChildOrSelf(IN_GUID aId)
626{
627 ComObjPtr<Snapshot> child;
628
629 AutoCaller autoCaller(this);
630 AssertComRC(autoCaller.rc());
631
632 // no need to lock, uuid is const
633 if (m->uuid == aId)
634 child = this;
635 else
636 {
637 for (SnapshotsList::const_iterator it = m->llChildren.begin();
638 it != m->llChildren.end();
639 ++it)
640 {
641 if ((child = (*it)->findChildOrSelf(aId)))
642 break;
643 }
644 }
645
646 return child;
647}
648
649/**
650 * Searches for a first snapshot with the given name among children,
651 * grand-children, etc. of this snapshot. This snapshot itself is also included
652 * in the search.
653 *
654 * Caller must hold the machine lock (which protects the snapshots tree!)
655 */
656ComObjPtr<Snapshot> Snapshot::findChildOrSelf(const Utf8Str &aName)
657{
658 ComObjPtr<Snapshot> child;
659 AssertReturn(!aName.isEmpty(), child);
660
661 AutoCaller autoCaller(this);
662 AssertComRC(autoCaller.rc());
663
664 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
665
666 if (m->strName == aName)
667 child = this;
668 else
669 {
670 alock.release();
671 for (SnapshotsList::const_iterator it = m->llChildren.begin();
672 it != m->llChildren.end();
673 ++it)
674 {
675 if ((child = (*it)->findChildOrSelf(aName)))
676 break;
677 }
678 }
679
680 return child;
681}
682
683/**
684 * Internal implementation for Snapshot::updateSavedStatePaths (below).
685 * @param aOldPath
686 * @param aNewPath
687 */
688void Snapshot::updateSavedStatePathsImpl(const char *aOldPath, const char *aNewPath)
689{
690 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
691
692 const Utf8Str &path = m->pMachine->mSSData->mStateFilePath;
693 LogFlowThisFunc(("Snap[%s].statePath={%s}\n", m->strName.c_str(), path.c_str()));
694
695 /* state file may be NULL (for offline snapshots) */
696 if ( path.length()
697 && RTPathStartsWith(path.c_str(), aOldPath)
698 )
699 {
700 m->pMachine->mSSData->mStateFilePath = Utf8StrFmt("%s%s", aNewPath, path.raw() + strlen(aOldPath));
701
702 LogFlowThisFunc(("-> updated: {%s}\n", path.raw()));
703 }
704
705 for (SnapshotsList::const_iterator it = m->llChildren.begin();
706 it != m->llChildren.end();
707 ++it)
708 {
709 Snapshot *pChild = *it;
710 pChild->updateSavedStatePathsImpl(aOldPath, aNewPath);
711 }
712}
713
714/**
715 * Checks if the specified path change affects the saved state file path of
716 * this snapshot or any of its (grand-)children and updates it accordingly.
717 *
718 * Intended to be called by Machine::openConfigLoader() only.
719 *
720 * @param aOldPath old path (full)
721 * @param aNewPath new path (full)
722 *
723 * @note Locks the machine (for the snapshots tree) + this object + children for writing.
724 */
725void Snapshot::updateSavedStatePaths(const char *aOldPath, const char *aNewPath)
726{
727 LogFlowThisFunc(("aOldPath={%s} aNewPath={%s}\n", aOldPath, aNewPath));
728
729 AssertReturnVoid(aOldPath);
730 AssertReturnVoid(aNewPath);
731
732 AutoCaller autoCaller(this);
733 AssertComRC(autoCaller.rc());
734
735 // snapshots tree is protected by machine lock
736 AutoWriteLock alock(m->pMachine COMMA_LOCKVAL_SRC_POS);
737
738 // call the implementation under the tree lock
739 updateSavedStatePathsImpl(aOldPath, aNewPath);
740}
741
742/**
743 * Internal implementation for Snapshot::saveSnapshot (below). Caller has
744 * requested the snapshots tree (machine) lock.
745 *
746 * @param aNode
747 * @param aAttrsOnly
748 * @return
749 */
750HRESULT Snapshot::saveSnapshotImpl(settings::Snapshot &data, bool aAttrsOnly)
751{
752 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
753
754 data.uuid = m->uuid;
755 data.strName = m->strName;
756 data.timestamp = m->timeStamp;
757 data.strDescription = m->strDescription;
758
759 if (aAttrsOnly)
760 return S_OK;
761
762 /* stateFile (optional) */
763 if (!stateFilePath().isEmpty())
764 /* try to make the file name relative to the settings file dir */
765 m->pMachine->calculateRelativePath(stateFilePath(), data.strStateFile);
766 else
767 data.strStateFile.setNull();
768
769 HRESULT rc = m->pMachine->saveHardware(data.hardware);
770 if (FAILED(rc)) return rc;
771
772 rc = m->pMachine->saveStorageControllers(data.storage);
773 if (FAILED(rc)) return rc;
774
775 alock.release();
776
777 data.llChildSnapshots.clear();
778
779 if (m->llChildren.size())
780 {
781 for (SnapshotsList::const_iterator it = m->llChildren.begin();
782 it != m->llChildren.end();
783 ++it)
784 {
785 settings::Snapshot snap;
786 rc = (*it)->saveSnapshotImpl(snap, aAttrsOnly);
787 if (FAILED(rc)) return rc;
788
789 data.llChildSnapshots.push_back(snap);
790 }
791 }
792
793 return S_OK;
794}
795
796/**
797 * Saves the given snapshot and all its children (unless \a aAttrsOnly is true).
798 * It is assumed that the given node is empty (unless \a aAttrsOnly is true).
799 *
800 * @param aNode <Snapshot> node to save the snapshot to.
801 * @param aSnapshot Snapshot to save.
802 * @param aAttrsOnly If true, only updatge user-changeable attrs.
803 */
804HRESULT Snapshot::saveSnapshot(settings::Snapshot &data, bool aAttrsOnly)
805{
806 // snapshots tree is protected by machine lock
807 AutoReadLock alock(m->pMachine COMMA_LOCKVAL_SRC_POS);
808
809 return saveSnapshotImpl(data, aAttrsOnly);
810}
811
812////////////////////////////////////////////////////////////////////////////////
813//
814// SnapshotMachine implementation
815//
816////////////////////////////////////////////////////////////////////////////////
817
818DEFINE_EMPTY_CTOR_DTOR(SnapshotMachine)
819
820HRESULT SnapshotMachine::FinalConstruct()
821{
822 LogFlowThisFunc(("\n"));
823
824 return S_OK;
825}
826
827void SnapshotMachine::FinalRelease()
828{
829 LogFlowThisFunc(("\n"));
830
831 uninit();
832}
833
834/**
835 * Initializes the SnapshotMachine object when taking a snapshot.
836 *
837 * @param aSessionMachine machine to take a snapshot from
838 * @param aSnapshotId snapshot ID of this snapshot machine
839 * @param aStateFilePath file where the execution state will be later saved
840 * (or NULL for the offline snapshot)
841 *
842 * @note The aSessionMachine must be locked for writing.
843 */
844HRESULT SnapshotMachine::init(SessionMachine *aSessionMachine,
845 IN_GUID aSnapshotId,
846 const Utf8Str &aStateFilePath)
847{
848 LogFlowThisFuncEnter();
849 LogFlowThisFunc(("mName={%ls}\n", aSessionMachine->mUserData->mName.raw()));
850
851 AssertReturn(aSessionMachine && !Guid(aSnapshotId).isEmpty(), E_INVALIDARG);
852
853 /* Enclose the state transition NotReady->InInit->Ready */
854 AutoInitSpan autoInitSpan(this);
855 AssertReturn(autoInitSpan.isOk(), E_FAIL);
856
857 AssertReturn(aSessionMachine->isWriteLockOnCurrentThread(), E_FAIL);
858
859 mSnapshotId = aSnapshotId;
860
861 /* memorize the primary Machine instance (i.e. not SessionMachine!) */
862 unconst(mPeer) = aSessionMachine->mPeer;
863 /* share the parent pointer */
864 unconst(mParent) = mPeer->mParent;
865
866 /* take the pointer to Data to share */
867 mData.share(mPeer->mData);
868
869 /* take the pointer to UserData to share (our UserData must always be the
870 * same as Machine's data) */
871 mUserData.share(mPeer->mUserData);
872 /* make a private copy of all other data (recent changes from SessionMachine) */
873 mHWData.attachCopy(aSessionMachine->mHWData);
874 mMediaData.attachCopy(aSessionMachine->mMediaData);
875
876 /* SSData is always unique for SnapshotMachine */
877 mSSData.allocate();
878 mSSData->mStateFilePath = aStateFilePath;
879
880 HRESULT rc = S_OK;
881
882 /* create copies of all shared folders (mHWData after attiching a copy
883 * contains just references to original objects) */
884 for (HWData::SharedFolderList::iterator it = mHWData->mSharedFolders.begin();
885 it != mHWData->mSharedFolders.end();
886 ++it)
887 {
888 ComObjPtr<SharedFolder> folder;
889 folder.createObject();
890 rc = folder->initCopy(this, *it);
891 if (FAILED(rc)) return rc;
892 *it = folder;
893 }
894
895 /* associate hard disks with the snapshot
896 * (Machine::uninitDataAndChildObjects() will deassociate at destruction) */
897 for (MediaData::AttachmentList::const_iterator it = mMediaData->mAttachments.begin();
898 it != mMediaData->mAttachments.end();
899 ++it)
900 {
901 MediumAttachment *pAtt = *it;
902 Medium *pMedium = pAtt->getMedium();
903 if (pMedium) // can be NULL for non-harddisk
904 {
905 rc = pMedium->attachTo(mData->mUuid, mSnapshotId);
906 AssertComRC(rc);
907 }
908 }
909
910 /* create copies of all storage controllers (mStorageControllerData
911 * after attaching a copy contains just references to original objects) */
912 mStorageControllers.allocate();
913 for (StorageControllerList::const_iterator
914 it = aSessionMachine->mStorageControllers->begin();
915 it != aSessionMachine->mStorageControllers->end();
916 ++it)
917 {
918 ComObjPtr<StorageController> ctrl;
919 ctrl.createObject();
920 ctrl->initCopy(this, *it);
921 mStorageControllers->push_back(ctrl);
922 }
923
924 /* create all other child objects that will be immutable private copies */
925
926 unconst(mBIOSSettings).createObject();
927 mBIOSSettings->initCopy(this, mPeer->mBIOSSettings);
928
929#ifdef VBOX_WITH_VRDP
930 unconst(mVRDPServer).createObject();
931 mVRDPServer->initCopy(this, mPeer->mVRDPServer);
932#endif
933
934 unconst(mAudioAdapter).createObject();
935 mAudioAdapter->initCopy(this, mPeer->mAudioAdapter);
936
937 unconst(mUSBController).createObject();
938 mUSBController->initCopy(this, mPeer->mUSBController);
939
940 for (ULONG slot = 0; slot < RT_ELEMENTS(mNetworkAdapters); slot++)
941 {
942 unconst(mNetworkAdapters[slot]).createObject();
943 mNetworkAdapters[slot]->initCopy(this, mPeer->mNetworkAdapters[slot]);
944 }
945
946 for (ULONG slot = 0; slot < RT_ELEMENTS(mSerialPorts); slot++)
947 {
948 unconst(mSerialPorts[slot]).createObject();
949 mSerialPorts[slot]->initCopy(this, mPeer->mSerialPorts[slot]);
950 }
951
952 for (ULONG slot = 0; slot < RT_ELEMENTS(mParallelPorts); slot++)
953 {
954 unconst(mParallelPorts[slot]).createObject();
955 mParallelPorts[slot]->initCopy(this, mPeer->mParallelPorts[slot]);
956 }
957
958 /* Confirm a successful initialization when it's the case */
959 autoInitSpan.setSucceeded();
960
961 LogFlowThisFuncLeave();
962 return S_OK;
963}
964
965/**
966 * Initializes the SnapshotMachine object when loading from the settings file.
967 *
968 * @param aMachine machine the snapshot belngs to
969 * @param aHWNode <Hardware> node
970 * @param aHDAsNode <HardDiskAttachments> node
971 * @param aSnapshotId snapshot ID of this snapshot machine
972 * @param aStateFilePath file where the execution state is saved
973 * (or NULL for the offline snapshot)
974 *
975 * @note Doesn't lock anything.
976 */
977HRESULT SnapshotMachine::init(Machine *aMachine,
978 const settings::Hardware &hardware,
979 const settings::Storage &storage,
980 IN_GUID aSnapshotId,
981 const Utf8Str &aStateFilePath)
982{
983 LogFlowThisFuncEnter();
984 LogFlowThisFunc(("mName={%ls}\n", aMachine->mUserData->mName.raw()));
985
986 AssertReturn(aMachine && !Guid(aSnapshotId).isEmpty(), E_INVALIDARG);
987
988 /* Enclose the state transition NotReady->InInit->Ready */
989 AutoInitSpan autoInitSpan(this);
990 AssertReturn(autoInitSpan.isOk(), E_FAIL);
991
992 /* Don't need to lock aMachine when VirtualBox is starting up */
993
994 mSnapshotId = aSnapshotId;
995
996 /* memorize the primary Machine instance */
997 unconst(mPeer) = aMachine;
998 /* share the parent pointer */
999 unconst(mParent) = mPeer->mParent;
1000
1001 /* take the pointer to Data to share */
1002 mData.share(mPeer->mData);
1003 /*
1004 * take the pointer to UserData to share
1005 * (our UserData must always be the same as Machine's data)
1006 */
1007 mUserData.share(mPeer->mUserData);
1008 /* allocate private copies of all other data (will be loaded from settings) */
1009 mHWData.allocate();
1010 mMediaData.allocate();
1011 mStorageControllers.allocate();
1012
1013 /* SSData is always unique for SnapshotMachine */
1014 mSSData.allocate();
1015 mSSData->mStateFilePath = aStateFilePath;
1016
1017 /* create all other child objects that will be immutable private copies */
1018
1019 unconst(mBIOSSettings).createObject();
1020 mBIOSSettings->init(this);
1021
1022#ifdef VBOX_WITH_VRDP
1023 unconst(mVRDPServer).createObject();
1024 mVRDPServer->init(this);
1025#endif
1026
1027 unconst(mAudioAdapter).createObject();
1028 mAudioAdapter->init(this);
1029
1030 unconst(mUSBController).createObject();
1031 mUSBController->init(this);
1032
1033 for (ULONG slot = 0; slot < RT_ELEMENTS(mNetworkAdapters); slot++)
1034 {
1035 unconst(mNetworkAdapters[slot]).createObject();
1036 mNetworkAdapters[slot]->init(this, slot);
1037 }
1038
1039 for (ULONG slot = 0; slot < RT_ELEMENTS(mSerialPorts); slot++)
1040 {
1041 unconst(mSerialPorts[slot]).createObject();
1042 mSerialPorts[slot]->init(this, slot);
1043 }
1044
1045 for (ULONG slot = 0; slot < RT_ELEMENTS(mParallelPorts); slot++)
1046 {
1047 unconst(mParallelPorts[slot]).createObject();
1048 mParallelPorts[slot]->init(this, slot);
1049 }
1050
1051 /* load hardware and harddisk settings */
1052
1053 HRESULT rc = loadHardware(hardware);
1054 if (SUCCEEDED(rc))
1055 rc = loadStorageControllers(storage, &mSnapshotId);
1056
1057 if (SUCCEEDED(rc))
1058 /* commit all changes made during the initialization */
1059 commit(); // @todo r=dj why do we need a commit in init?!? this is very expensive
1060
1061 /* Confirm a successful initialization when it's the case */
1062 if (SUCCEEDED(rc))
1063 autoInitSpan.setSucceeded();
1064
1065 LogFlowThisFuncLeave();
1066 return rc;
1067}
1068
1069/**
1070 * Uninitializes this SnapshotMachine object.
1071 */
1072void SnapshotMachine::uninit()
1073{
1074 LogFlowThisFuncEnter();
1075
1076 /* Enclose the state transition Ready->InUninit->NotReady */
1077 AutoUninitSpan autoUninitSpan(this);
1078 if (autoUninitSpan.uninitDone())
1079 return;
1080
1081 uninitDataAndChildObjects();
1082
1083 /* free the essential data structure last */
1084 mData.free();
1085
1086 unconst(mParent) = NULL;
1087 unconst(mPeer) = NULL;
1088
1089 LogFlowThisFuncLeave();
1090}
1091
1092/**
1093 * Overrides VirtualBoxBase::lockHandle() in order to share the lock handle
1094 * with the primary Machine instance (mPeer).
1095 */
1096RWLockHandle *SnapshotMachine::lockHandle() const
1097{
1098 AssertReturn(mPeer != NULL, NULL);
1099 return mPeer->lockHandle();
1100}
1101
1102////////////////////////////////////////////////////////////////////////////////
1103//
1104// SnapshotMachine public internal methods
1105//
1106////////////////////////////////////////////////////////////////////////////////
1107
1108/**
1109 * Called by the snapshot object associated with this SnapshotMachine when
1110 * snapshot data such as name or description is changed.
1111 *
1112 * @note Locks this object for writing.
1113 */
1114HRESULT SnapshotMachine::onSnapshotChange(Snapshot *aSnapshot)
1115{
1116 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1117
1118 // mPeer->saveAllSnapshots(); @todo
1119
1120 /* inform callbacks */
1121 mParent->onSnapshotChange(mData->mUuid, aSnapshot->getId());
1122
1123 return S_OK;
1124}
1125
1126////////////////////////////////////////////////////////////////////////////////
1127//
1128// SessionMachine task records
1129//
1130////////////////////////////////////////////////////////////////////////////////
1131
1132/**
1133 * Abstract base class for SessionMachine::RestoreSnapshotTask and
1134 * SessionMachine::DeleteSnapshotTask. This is necessary since
1135 * RTThreadCreate cannot call a method as its thread function, so
1136 * instead we have it call the static SessionMachine::taskHandler,
1137 * which can then call the handler() method in here (implemented
1138 * by the children).
1139 */
1140struct SessionMachine::SnapshotTask
1141{
1142 SnapshotTask(SessionMachine *m,
1143 Progress *p,
1144 Snapshot *s)
1145 : pMachine(m),
1146 pProgress(p),
1147 machineStateBackup(m->mData->mMachineState), // save the current machine state
1148 pSnapshot(s)
1149 {}
1150
1151 void modifyBackedUpState(MachineState_T s)
1152 {
1153 *const_cast<MachineState_T*>(&machineStateBackup) = s;
1154 }
1155
1156 virtual void handler() = 0;
1157
1158 ComObjPtr<SessionMachine> pMachine;
1159 ComObjPtr<Progress> pProgress;
1160 const MachineState_T machineStateBackup;
1161 ComObjPtr<Snapshot> pSnapshot;
1162};
1163
1164/** Restore snapshot state task */
1165struct SessionMachine::RestoreSnapshotTask
1166 : public SessionMachine::SnapshotTask
1167{
1168 RestoreSnapshotTask(SessionMachine *m,
1169 Progress *p,
1170 Snapshot *s,
1171 ULONG ulStateFileSizeMB)
1172 : SnapshotTask(m, p, s),
1173 m_ulStateFileSizeMB(ulStateFileSizeMB)
1174 {}
1175
1176 void handler()
1177 {
1178 pMachine->restoreSnapshotHandler(*this);
1179 }
1180
1181 ULONG m_ulStateFileSizeMB;
1182};
1183
1184/** Delete snapshot task */
1185struct SessionMachine::DeleteSnapshotTask
1186 : public SessionMachine::SnapshotTask
1187{
1188 DeleteSnapshotTask(SessionMachine *m,
1189 Progress *p,
1190 bool fDeleteOnline,
1191 Snapshot *s)
1192 : SnapshotTask(m, p, s),
1193 m_fDeleteOnline(fDeleteOnline)
1194 {}
1195
1196 void handler()
1197 {
1198 pMachine->deleteSnapshotHandler(*this);
1199 }
1200
1201 bool m_fDeleteOnline;
1202};
1203
1204/**
1205 * Static SessionMachine method that can get passed to RTThreadCreate to
1206 * have a thread started for a SnapshotTask. See SnapshotTask above.
1207 *
1208 * This calls either RestoreSnapshotTask::handler() or DeleteSnapshotTask::handler().
1209 */
1210
1211/* static */ DECLCALLBACK(int) SessionMachine::taskHandler(RTTHREAD /* thread */, void *pvUser)
1212{
1213 AssertReturn(pvUser, VERR_INVALID_POINTER);
1214
1215 SnapshotTask *task = static_cast<SnapshotTask*>(pvUser);
1216 task->handler();
1217
1218 // it's our responsibility to delete the task
1219 delete task;
1220
1221 return 0;
1222}
1223
1224////////////////////////////////////////////////////////////////////////////////
1225//
1226// TakeSnapshot methods (SessionMachine and related tasks)
1227//
1228////////////////////////////////////////////////////////////////////////////////
1229
1230/**
1231 * Implementation for IInternalMachineControl::beginTakingSnapshot().
1232 *
1233 * Gets called indirectly from Console::TakeSnapshot, which creates a
1234 * progress object in the client and then starts a thread
1235 * (Console::fntTakeSnapshotWorker) which then calls this.
1236 *
1237 * In other words, the asynchronous work for taking snapshots takes place
1238 * on the _client_ (in the Console). This is different from restoring
1239 * or deleting snapshots, which start threads on the server.
1240 *
1241 * This does the server-side work of taking a snapshot: it creates diffencing
1242 * images for all hard disks attached to the machine and then creates a
1243 * Snapshot object with a corresponding SnapshotMachine to save the VM settings.
1244 *
1245 * The client's fntTakeSnapshotWorker() blocks while this takes place.
1246 * After this returns successfully, fntTakeSnapshotWorker() will begin
1247 * saving the machine state to the snapshot object and reconfigure the
1248 * hard disks.
1249 *
1250 * When the console is done, it calls SessionMachine::EndTakingSnapshot().
1251 *
1252 * @note Locks mParent + this object for writing.
1253 *
1254 * @param aInitiator in: The console on which Console::TakeSnapshot was called.
1255 * @param aName in: The name for the new snapshot.
1256 * @param aDescription in: A description for the new snapshot.
1257 * @param aConsoleProgress in: The console's (client's) progress object.
1258 * @param fTakingSnapshotOnline in: True if an online snapshot is being taken (i.e. machine is running).
1259 * @param aStateFilePath out: name of file in snapshots folder to which the console should write the VM state.
1260 * @return
1261 */
1262STDMETHODIMP SessionMachine::BeginTakingSnapshot(IConsole *aInitiator,
1263 IN_BSTR aName,
1264 IN_BSTR aDescription,
1265 IProgress *aConsoleProgress,
1266 BOOL fTakingSnapshotOnline,
1267 BSTR *aStateFilePath)
1268{
1269 LogFlowThisFuncEnter();
1270
1271 AssertReturn(aInitiator && aName, E_INVALIDARG);
1272 AssertReturn(aStateFilePath, E_POINTER);
1273
1274 LogFlowThisFunc(("aName='%ls' fTakingSnapshotOnline=%RTbool\n", aName, fTakingSnapshotOnline));
1275
1276 AutoCaller autoCaller(this);
1277 AssertComRCReturn(autoCaller.rc(), autoCaller.rc());
1278
1279 // if this becomes true, we need to call VirtualBox::saveSettings() in the end
1280 bool fNeedsSaveSettings = false;
1281
1282 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1283
1284 AssertReturn( !Global::IsOnlineOrTransient(mData->mMachineState)
1285 || mData->mMachineState == MachineState_Running
1286 || mData->mMachineState == MachineState_Paused, E_FAIL);
1287 AssertReturn(mSnapshotData.mLastState == MachineState_Null, E_FAIL);
1288 AssertReturn(mSnapshotData.mSnapshot.isNull(), E_FAIL);
1289
1290 if ( !fTakingSnapshotOnline
1291 && mData->mMachineState != MachineState_Saved
1292 )
1293 {
1294 /* save all current settings to ensure current changes are committed and
1295 * hard disks are fixed up */
1296 HRESULT rc = saveSettings(NULL);
1297 // no need to check for whether VirtualBox.xml needs changing since
1298 // we can't have a machine XML rename pending at this point
1299 if (FAILED(rc)) return rc;
1300 }
1301
1302 /* create an ID for the snapshot */
1303 Guid snapshotId;
1304 snapshotId.create();
1305
1306 Utf8Str strStateFilePath;
1307 /* stateFilePath is null when the machine is not online nor saved */
1308 if ( fTakingSnapshotOnline
1309 || mData->mMachineState == MachineState_Saved)
1310 {
1311 strStateFilePath = Utf8StrFmt("%ls%c{%RTuuid}.sav",
1312 mUserData->mSnapshotFolderFull.raw(),
1313 RTPATH_DELIMITER,
1314 snapshotId.ptr());
1315 /* ensure the directory for the saved state file exists */
1316 HRESULT rc = VirtualBox::ensureFilePathExists(strStateFilePath);
1317 if (FAILED(rc)) return rc;
1318 }
1319
1320 /* create a snapshot machine object */
1321 ComObjPtr<SnapshotMachine> snapshotMachine;
1322 snapshotMachine.createObject();
1323 HRESULT rc = snapshotMachine->init(this, snapshotId, strStateFilePath);
1324 AssertComRCReturn(rc, rc);
1325
1326 /* create a snapshot object */
1327 RTTIMESPEC time;
1328 ComObjPtr<Snapshot> pSnapshot;
1329 pSnapshot.createObject();
1330 rc = pSnapshot->init(mParent,
1331 snapshotId,
1332 aName,
1333 aDescription,
1334 *RTTimeNow(&time),
1335 snapshotMachine,
1336 mData->mCurrentSnapshot);
1337 AssertComRCReturnRC(rc);
1338
1339 /* fill in the snapshot data */
1340 mSnapshotData.mLastState = mData->mMachineState;
1341 mSnapshotData.mSnapshot = pSnapshot;
1342
1343 try
1344 {
1345 LogFlowThisFunc(("Creating differencing hard disks (online=%d)...\n",
1346 fTakingSnapshotOnline));
1347
1348 // backup the media data so we can recover if things goes wrong along the day;
1349 // the matching commit() is in fixupMedia() during endSnapshot()
1350 setModified(IsModified_Storage);
1351 mMediaData.backup();
1352
1353 /* Console::fntTakeSnapshotWorker and friends expects this. */
1354 if (mSnapshotData.mLastState == MachineState_Running)
1355 setMachineState(MachineState_LiveSnapshotting);
1356 else
1357 setMachineState(MachineState_Saving); /** @todo Confusing! Saving is used for both online and offline snapshots. */
1358
1359 /* create new differencing hard disks and attach them to this machine */
1360 rc = createImplicitDiffs(mUserData->mSnapshotFolderFull,
1361 aConsoleProgress,
1362 1, // operation weight; must be the same as in Console::TakeSnapshot()
1363 !!fTakingSnapshotOnline,
1364 &fNeedsSaveSettings);
1365 if (FAILED(rc))
1366 throw rc;
1367
1368 if (mSnapshotData.mLastState == MachineState_Saved)
1369 {
1370 Utf8Str stateFrom = mSSData->mStateFilePath;
1371 Utf8Str stateTo = mSnapshotData.mSnapshot->stateFilePath();
1372
1373 LogFlowThisFunc(("Copying the execution state from '%s' to '%s'...\n",
1374 stateFrom.raw(), stateTo.raw()));
1375
1376 aConsoleProgress->SetNextOperation(Bstr(tr("Copying the execution state")),
1377 1); // weight
1378
1379 /* Leave the lock before a lengthy operation (machine is protected
1380 * by "Saving" machine state now) */
1381 alock.release();
1382
1383 /* copy the state file */
1384 int vrc = RTFileCopyEx(stateFrom.c_str(),
1385 stateTo.c_str(),
1386 0,
1387 progressCallback,
1388 aConsoleProgress);
1389 alock.acquire();
1390
1391 if (RT_FAILURE(vrc))
1392 /** @todo r=bird: Delete stateTo when appropriate. */
1393 throw setError(E_FAIL,
1394 tr("Could not copy the state file '%s' to '%s' (%Rrc)"),
1395 stateFrom.raw(),
1396 stateTo.raw(),
1397 vrc);
1398 }
1399 }
1400 catch (HRESULT hrc)
1401 {
1402 LogThisFunc(("Caught %Rhrc [%s]\n", hrc, Global::stringifyMachineState(mData->mMachineState) ));
1403 if ( mSnapshotData.mLastState != mData->mMachineState
1404 && ( mSnapshotData.mLastState == MachineState_Running
1405 ? mData->mMachineState == MachineState_LiveSnapshotting
1406 : mData->mMachineState == MachineState_Saving)
1407 )
1408 setMachineState(mSnapshotData.mLastState);
1409
1410 pSnapshot->uninit();
1411 pSnapshot.setNull();
1412 mSnapshotData.mLastState = MachineState_Null;
1413 mSnapshotData.mSnapshot.setNull();
1414
1415 rc = hrc;
1416
1417 // @todo r=dj what with the implicit diff that we created above? this is never cleaned up
1418 }
1419
1420 if (fTakingSnapshotOnline && SUCCEEDED(rc))
1421 strStateFilePath.cloneTo(aStateFilePath);
1422 else
1423 *aStateFilePath = NULL;
1424
1425 // @todo r=dj normally we would need to save the settings if fNeedsSaveSettings was set to true,
1426 // but since we have no error handling that cleans up the diff image that might have gotten created,
1427 // there's no point in saving the disk registry at this point either... this needs fixing.
1428
1429 LogFlowThisFunc(("LEAVE - %Rhrc [%s]\n", rc, Global::stringifyMachineState(mData->mMachineState) ));
1430 return rc;
1431}
1432
1433/**
1434 * Implementation for IInternalMachineControl::endTakingSnapshot().
1435 *
1436 * Called by the Console when it's done saving the VM state into the snapshot
1437 * (if online) and reconfiguring the hard disks. See BeginTakingSnapshot() above.
1438 *
1439 * This also gets called if the console part of snapshotting failed after the
1440 * BeginTakingSnapshot() call, to clean up the server side.
1441 *
1442 * @note Locks VirtualBox and this object for writing.
1443 *
1444 * @param aSuccess Whether Console was successful with the client-side snapshot things.
1445 * @return
1446 */
1447STDMETHODIMP SessionMachine::EndTakingSnapshot(BOOL aSuccess)
1448{
1449 LogFlowThisFunc(("\n"));
1450
1451 AutoCaller autoCaller(this);
1452 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
1453
1454 AutoWriteLock machineLock(this COMMA_LOCKVAL_SRC_POS);
1455
1456 AssertReturn( !aSuccess
1457 || ( ( mData->mMachineState == MachineState_Saving
1458 || mData->mMachineState == MachineState_LiveSnapshotting)
1459 && mSnapshotData.mLastState != MachineState_Null
1460 && !mSnapshotData.mSnapshot.isNull()
1461 )
1462 , E_FAIL);
1463
1464 /*
1465 * Restore the state we had when BeginTakingSnapshot() was called,
1466 * Console::fntTakeSnapshotWorker restores its local copy when we return.
1467 * If the state was Running, then let Console::fntTakeSnapshotWorker do it
1468 * all to avoid races.
1469 */
1470 if ( mData->mMachineState != mSnapshotData.mLastState
1471 && mSnapshotData.mLastState != MachineState_Running
1472 )
1473 setMachineState(mSnapshotData.mLastState);
1474
1475 ComObjPtr<Snapshot> pOldFirstSnap = mData->mFirstSnapshot;
1476 ComObjPtr<Snapshot> pOldCurrentSnap = mData->mCurrentSnapshot;
1477
1478 bool fOnline = Global::IsOnline(mSnapshotData.mLastState);
1479
1480 HRESULT rc = S_OK;
1481
1482 if (aSuccess)
1483 {
1484 // new snapshot becomes the current one
1485 mData->mCurrentSnapshot = mSnapshotData.mSnapshot;
1486
1487 /* memorize the first snapshot if necessary */
1488 if (!mData->mFirstSnapshot)
1489 mData->mFirstSnapshot = mData->mCurrentSnapshot;
1490
1491 int flSaveSettings = SaveS_Force; // do not do a deep compare in machine settings,
1492 // snapshots change, so we know we need to save
1493 if (!fOnline)
1494 /* the machine was powered off or saved when taking a snapshot, so
1495 * reset the mCurrentStateModified flag */
1496 flSaveSettings |= SaveS_ResetCurStateModified;
1497
1498 rc = saveSettings(NULL, flSaveSettings);
1499 // no need to change for whether VirtualBox.xml needs saving since
1500 // we'll save the global settings below anyway
1501 }
1502
1503 if (aSuccess && SUCCEEDED(rc))
1504 {
1505 /* associate old hard disks with the snapshot and do locking/unlocking*/
1506 commitMedia(fOnline);
1507
1508 /* inform callbacks */
1509 mParent->onSnapshotTaken(mData->mUuid,
1510 mSnapshotData.mSnapshot->getId());
1511 }
1512 else
1513 {
1514 /* delete all differencing hard disks created (this will also attach
1515 * their parents back by rolling back mMediaData) */
1516 rollbackMedia();
1517
1518 mData->mFirstSnapshot = pOldFirstSnap; // might have been changed above
1519 mData->mCurrentSnapshot = pOldCurrentSnap; // might have been changed above
1520
1521 /* delete the saved state file (it might have been already created) */
1522 if (mSnapshotData.mSnapshot->stateFilePath().length())
1523 RTFileDelete(mSnapshotData.mSnapshot->stateFilePath().c_str());
1524
1525 mSnapshotData.mSnapshot->uninit();
1526 }
1527
1528 /* clear out the snapshot data */
1529 mSnapshotData.mLastState = MachineState_Null;
1530 mSnapshotData.mSnapshot.setNull();
1531
1532 // save VirtualBox.xml (media registry most probably changed with diff image)
1533 machineLock.release();
1534 AutoWriteLock vboxLock(mParent COMMA_LOCKVAL_SRC_POS);
1535 mParent->saveSettings();
1536
1537 return rc;
1538}
1539
1540////////////////////////////////////////////////////////////////////////////////
1541//
1542// RestoreSnapshot methods (SessionMachine and related tasks)
1543//
1544////////////////////////////////////////////////////////////////////////////////
1545
1546/**
1547 * Implementation for IInternalMachineControl::restoreSnapshot().
1548 *
1549 * Gets called from Console::RestoreSnapshot(), and that's basically the
1550 * only thing Console does. Restoring a snapshot happens entirely on the
1551 * server side since the machine cannot be running.
1552 *
1553 * This creates a new thread that does the work and returns a progress
1554 * object to the client which is then returned to the caller of
1555 * Console::RestoreSnapshot().
1556 *
1557 * Actual work then takes place in RestoreSnapshotTask::handler().
1558 *
1559 * @note Locks this + children objects for writing!
1560 *
1561 * @param aInitiator in: rhe console on which Console::RestoreSnapshot was called.
1562 * @param aSnapshot in: the snapshot to restore.
1563 * @param aMachineState in: client-side machine state.
1564 * @param aProgress out: progress object to monitor restore thread.
1565 * @return
1566 */
1567STDMETHODIMP SessionMachine::RestoreSnapshot(IConsole *aInitiator,
1568 ISnapshot *aSnapshot,
1569 MachineState_T *aMachineState,
1570 IProgress **aProgress)
1571{
1572 LogFlowThisFuncEnter();
1573
1574 AssertReturn(aInitiator, E_INVALIDARG);
1575 AssertReturn(aSnapshot && aMachineState && aProgress, E_POINTER);
1576
1577 AutoCaller autoCaller(this);
1578 AssertComRCReturn(autoCaller.rc(), autoCaller.rc());
1579
1580 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1581
1582 // machine must not be running
1583 ComAssertRet(!Global::IsOnlineOrTransient(mData->mMachineState),
1584 E_FAIL);
1585
1586 ComObjPtr<Snapshot> pSnapshot(static_cast<Snapshot*>(aSnapshot));
1587 ComObjPtr<SnapshotMachine> pSnapMachine = pSnapshot->getSnapshotMachine();
1588
1589 // create a progress object. The number of operations is:
1590 // 1 (preparing) + # of hard disks + 1 (if we need to copy the saved state file) */
1591 LogFlowThisFunc(("Going thru snapshot machine attachments to determine progress setup\n"));
1592
1593 ULONG ulOpCount = 1; // one for preparations
1594 ULONG ulTotalWeight = 1; // one for preparations
1595 for (MediaData::AttachmentList::iterator it = pSnapMachine->mMediaData->mAttachments.begin();
1596 it != pSnapMachine->mMediaData->mAttachments.end();
1597 ++it)
1598 {
1599 ComObjPtr<MediumAttachment> &pAttach = *it;
1600 AutoReadLock attachLock(pAttach COMMA_LOCKVAL_SRC_POS);
1601 if (pAttach->getType() == DeviceType_HardDisk)
1602 {
1603 ++ulOpCount;
1604 ++ulTotalWeight; // assume one MB weight for each differencing hard disk to manage
1605 Assert(pAttach->getMedium());
1606 LogFlowThisFunc(("op %d: considering hard disk attachment %s\n", ulOpCount, pAttach->getMedium()->getName().c_str()));
1607 }
1608 }
1609
1610 ULONG ulStateFileSizeMB = 0;
1611 if (pSnapshot->stateFilePath().length())
1612 {
1613 ++ulOpCount; // one for the saved state
1614
1615 uint64_t ullSize;
1616 int irc = RTFileQuerySize(pSnapshot->stateFilePath().c_str(), &ullSize);
1617 if (!RT_SUCCESS(irc))
1618 // if we can't access the file here, then we'll be doomed later also, so fail right away
1619 setError(E_FAIL, tr("Cannot access state file '%s', runtime error, %Rra"), pSnapshot->stateFilePath().c_str(), irc);
1620 if (ullSize == 0) // avoid division by zero
1621 ullSize = _1M;
1622
1623 ulStateFileSizeMB = (ULONG)(ullSize / _1M);
1624 LogFlowThisFunc(("op %d: saved state file '%s' has %RI64 bytes (%d MB)\n",
1625 ulOpCount, pSnapshot->stateFilePath().raw(), ullSize, ulStateFileSizeMB));
1626
1627 ulTotalWeight += ulStateFileSizeMB;
1628 }
1629
1630 ComObjPtr<Progress> pProgress;
1631 pProgress.createObject();
1632 pProgress->init(mParent, aInitiator,
1633 BstrFmt(tr("Restoring snapshot '%s'"), pSnapshot->getName().c_str()),
1634 FALSE /* aCancelable */,
1635 ulOpCount,
1636 ulTotalWeight,
1637 Bstr(tr("Restoring machine settings")),
1638 1);
1639
1640 /* create and start the task on a separate thread (note that it will not
1641 * start working until we release alock) */
1642 RestoreSnapshotTask *task = new RestoreSnapshotTask(this,
1643 pProgress,
1644 pSnapshot,
1645 ulStateFileSizeMB);
1646 int vrc = RTThreadCreate(NULL,
1647 taskHandler,
1648 (void*)task,
1649 0,
1650 RTTHREADTYPE_MAIN_WORKER,
1651 0,
1652 "RestoreSnap");
1653 if (RT_FAILURE(vrc))
1654 {
1655 delete task;
1656 ComAssertRCRet(vrc, E_FAIL);
1657 }
1658
1659 /* set the proper machine state (note: after creating a Task instance) */
1660 setMachineState(MachineState_RestoringSnapshot);
1661
1662 /* return the progress to the caller */
1663 pProgress.queryInterfaceTo(aProgress);
1664
1665 /* return the new state to the caller */
1666 *aMachineState = mData->mMachineState;
1667
1668 LogFlowThisFuncLeave();
1669
1670 return S_OK;
1671}
1672
1673/**
1674 * Worker method for the restore snapshot thread created by SessionMachine::RestoreSnapshot().
1675 * This method gets called indirectly through SessionMachine::taskHandler() which then
1676 * calls RestoreSnapshotTask::handler().
1677 *
1678 * The RestoreSnapshotTask contains the progress object returned to the console by
1679 * SessionMachine::RestoreSnapshot, through which progress and results are reported.
1680 *
1681 * @note Locks mParent + this object for writing.
1682 *
1683 * @param aTask Task data.
1684 */
1685void SessionMachine::restoreSnapshotHandler(RestoreSnapshotTask &aTask)
1686{
1687 LogFlowThisFuncEnter();
1688
1689 AutoCaller autoCaller(this);
1690
1691 LogFlowThisFunc(("state=%d\n", autoCaller.state()));
1692 if (!autoCaller.isOk())
1693 {
1694 /* we might have been uninitialized because the session was accidentally
1695 * closed by the client, so don't assert */
1696 aTask.pProgress->notifyComplete(E_FAIL,
1697 COM_IIDOF(IMachine),
1698 getComponentName(),
1699 tr("The session has been accidentally closed"));
1700
1701 LogFlowThisFuncLeave();
1702 return;
1703 }
1704
1705 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1706
1707 /* discard all current changes to mUserData (name, OSType etc.) (note that
1708 * the machine is powered off, so there is no need to inform the direct
1709 * session) */
1710 if (mData->flModifications)
1711 rollback(false /* aNotify */);
1712
1713 HRESULT rc = S_OK;
1714
1715 bool stateRestored = false;
1716 bool fNeedsSaveSettings = false;
1717
1718 try
1719 {
1720 /* Delete the saved state file if the machine was Saved prior to this
1721 * operation */
1722 if (aTask.machineStateBackup == MachineState_Saved)
1723 {
1724 Assert(!mSSData->mStateFilePath.isEmpty());
1725 RTFileDelete(mSSData->mStateFilePath.c_str());
1726 mSSData->mStateFilePath.setNull();
1727 aTask.modifyBackedUpState(MachineState_PoweredOff);
1728 rc = saveStateSettings(SaveSTS_StateFilePath);
1729 if (FAILED(rc))
1730 throw rc;
1731 }
1732
1733 RTTIMESPEC snapshotTimeStamp;
1734 RTTimeSpecSetMilli(&snapshotTimeStamp, 0);
1735
1736 {
1737 AutoReadLock snapshotLock(aTask.pSnapshot COMMA_LOCKVAL_SRC_POS);
1738
1739 /* remember the timestamp of the snapshot we're restoring from */
1740 snapshotTimeStamp = aTask.pSnapshot->getTimeStamp();
1741
1742 ComPtr<SnapshotMachine> pSnapshotMachine(aTask.pSnapshot->getSnapshotMachine());
1743
1744 /* copy all hardware data from the snapshot */
1745 copyFrom(pSnapshotMachine);
1746
1747 LogFlowThisFunc(("Restoring hard disks from the snapshot...\n"));
1748
1749 // restore the attachments from the snapshot
1750 setModified(IsModified_Storage);
1751 mMediaData.backup();
1752 mMediaData->mAttachments = pSnapshotMachine->mMediaData->mAttachments;
1753
1754 /* leave the locks before the potentially lengthy operation */
1755 snapshotLock.release();
1756 alock.leave();
1757
1758 rc = createImplicitDiffs(mUserData->mSnapshotFolderFull,
1759 aTask.pProgress,
1760 1,
1761 false /* aOnline */,
1762 &fNeedsSaveSettings);
1763 if (FAILED(rc))
1764 throw rc;
1765
1766 alock.enter();
1767 snapshotLock.acquire();
1768
1769 /* Note: on success, current (old) hard disks will be
1770 * deassociated/deleted on #commit() called from #saveSettings() at
1771 * the end. On failure, newly created implicit diffs will be
1772 * deleted by #rollback() at the end. */
1773
1774 /* should not have a saved state file associated at this point */
1775 Assert(mSSData->mStateFilePath.isEmpty());
1776
1777 if (!aTask.pSnapshot->stateFilePath().isEmpty())
1778 {
1779 Utf8Str snapStateFilePath = aTask.pSnapshot->stateFilePath();
1780
1781 Utf8Str stateFilePath = Utf8StrFmt("%ls%c{%RTuuid}.sav",
1782 mUserData->mSnapshotFolderFull.raw(),
1783 RTPATH_DELIMITER,
1784 mData->mUuid.raw());
1785
1786 LogFlowThisFunc(("Copying saved state file from '%s' to '%s'...\n",
1787 snapStateFilePath.raw(), stateFilePath.raw()));
1788
1789 aTask.pProgress->SetNextOperation(Bstr(tr("Restoring the execution state")),
1790 aTask.m_ulStateFileSizeMB); // weight
1791
1792 /* leave the lock before the potentially lengthy operation */
1793 snapshotLock.release();
1794 alock.leave();
1795
1796 /* copy the state file */
1797 int vrc = RTFileCopyEx(snapStateFilePath.c_str(),
1798 stateFilePath.c_str(),
1799 0,
1800 progressCallback,
1801 static_cast<IProgress*>(aTask.pProgress));
1802
1803 alock.enter();
1804 snapshotLock.acquire();
1805
1806 if (RT_SUCCESS(vrc))
1807 mSSData->mStateFilePath = stateFilePath;
1808 else
1809 throw setError(E_FAIL,
1810 tr("Could not copy the state file '%s' to '%s' (%Rrc)"),
1811 snapStateFilePath.raw(),
1812 stateFilePath.raw(),
1813 vrc);
1814 }
1815
1816 LogFlowThisFunc(("Setting new current snapshot {%RTuuid}\n", aTask.pSnapshot->getId().raw()));
1817 /* make the snapshot we restored from the current snapshot */
1818 mData->mCurrentSnapshot = aTask.pSnapshot;
1819 }
1820
1821 /* grab differencing hard disks from the old attachments that will
1822 * become unused and need to be auto-deleted */
1823 std::list< ComObjPtr<MediumAttachment> > llDiffAttachmentsToDelete;
1824
1825 for (MediaData::AttachmentList::const_iterator it = mMediaData.backedUpData()->mAttachments.begin();
1826 it != mMediaData.backedUpData()->mAttachments.end();
1827 ++it)
1828 {
1829 ComObjPtr<MediumAttachment> pAttach = *it;
1830 ComObjPtr<Medium> pMedium = pAttach->getMedium();
1831
1832 /* while the hard disk is attached, the number of children or the
1833 * parent cannot change, so no lock */
1834 if ( !pMedium.isNull()
1835 && pAttach->getType() == DeviceType_HardDisk
1836 && !pMedium->getParent().isNull()
1837 && pMedium->getChildren().size() == 0
1838 )
1839 {
1840 LogFlowThisFunc(("Picked differencing image '%s' for deletion\n", pMedium->getName().raw()));
1841
1842 llDiffAttachmentsToDelete.push_back(pAttach);
1843 }
1844 }
1845
1846 int saveFlags = 0;
1847
1848 /* we have already deleted the current state, so set the execution
1849 * state accordingly no matter of the delete snapshot result */
1850 if (!mSSData->mStateFilePath.isEmpty())
1851 setMachineState(MachineState_Saved);
1852 else
1853 setMachineState(MachineState_PoweredOff);
1854
1855 updateMachineStateOnClient();
1856 stateRestored = true;
1857
1858 /* assign the timestamp from the snapshot */
1859 Assert(RTTimeSpecGetMilli (&snapshotTimeStamp) != 0);
1860 mData->mLastStateChange = snapshotTimeStamp;
1861
1862 // detach the current-state diffs that we detected above and build a list of
1863 // image files to delete _after_ saveSettings()
1864
1865 MediaList llDiffsToDelete;
1866
1867 for (std::list< ComObjPtr<MediumAttachment> >::iterator it = llDiffAttachmentsToDelete.begin();
1868 it != llDiffAttachmentsToDelete.end();
1869 ++it)
1870 {
1871 ComObjPtr<MediumAttachment> pAttach = *it; // guaranteed to have only attachments where medium != NULL
1872 ComObjPtr<Medium> pMedium = pAttach->getMedium();
1873
1874 AutoWriteLock mlock(pMedium COMMA_LOCKVAL_SRC_POS);
1875
1876 LogFlowThisFunc(("Detaching old current state in differencing image '%s'\n", pMedium->getName().raw()));
1877
1878 // Normally we "detach" the medium by removing the attachment object
1879 // from the current machine data; saveSettings() below would then
1880 // compare the current machine data with the one in the backup
1881 // and actually call Medium::detachFrom(). But that works only half
1882 // the time in our case so instead we force a detachment here:
1883 // remove from machine data
1884 mMediaData->mAttachments.remove(pAttach);
1885 // remove it from the backup or else saveSettings will try to detach
1886 // it again and assert
1887 mMediaData.backedUpData()->mAttachments.remove(pAttach);
1888 // then clean up backrefs
1889 pMedium->detachFrom(mData->mUuid);
1890
1891 llDiffsToDelete.push_back(pMedium);
1892 }
1893
1894 // save machine settings, reset the modified flag and commit;
1895 rc = saveSettings(&fNeedsSaveSettings, SaveS_ResetCurStateModified | saveFlags);
1896 if (FAILED(rc))
1897 throw rc;
1898
1899 // let go of the locks while we're deleting image files below
1900 alock.leave();
1901 // from here on we cannot roll back on failure any more
1902
1903 for (MediaList::iterator it = llDiffsToDelete.begin();
1904 it != llDiffsToDelete.end();
1905 ++it)
1906 {
1907 ComObjPtr<Medium> &pMedium = *it;
1908 LogFlowThisFunc(("Deleting old current state in differencing image '%s'\n", pMedium->getName().raw()));
1909
1910 HRESULT rc2 = pMedium->deleteStorage(NULL /* aProgress */,
1911 true /* aWait */,
1912 &fNeedsSaveSettings);
1913 // ignore errors here because we cannot roll back after saveSettings() above
1914 if (SUCCEEDED(rc2))
1915 pMedium->uninit();
1916 }
1917
1918 if (fNeedsSaveSettings)
1919 {
1920 // finally, VirtualBox.xml needs saving too
1921 AutoWriteLock vboxLock(mParent COMMA_LOCKVAL_SRC_POS);
1922 mParent->saveSettings();
1923 }
1924 }
1925 catch (HRESULT aRC)
1926 {
1927 rc = aRC;
1928 }
1929
1930 if (FAILED(rc))
1931 {
1932 /* preserve existing error info */
1933 ErrorInfoKeeper eik;
1934
1935 /* undo all changes on failure */
1936 rollback(false /* aNotify */);
1937
1938 if (!stateRestored)
1939 {
1940 /* restore the machine state */
1941 setMachineState(aTask.machineStateBackup);
1942 updateMachineStateOnClient();
1943 }
1944 }
1945
1946 /* set the result (this will try to fetch current error info on failure) */
1947 aTask.pProgress->notifyComplete(rc);
1948
1949 if (SUCCEEDED(rc))
1950 mParent->onSnapshotDeleted(mData->mUuid, Guid());
1951
1952 LogFlowThisFunc(("Done restoring snapshot (rc=%08X)\n", rc));
1953
1954 LogFlowThisFuncLeave();
1955}
1956
1957////////////////////////////////////////////////////////////////////////////////
1958//
1959// DeleteSnapshot methods (SessionMachine and related tasks)
1960//
1961////////////////////////////////////////////////////////////////////////////////
1962
1963/**
1964 * Implementation for IInternalMachineControl::deleteSnapshot().
1965 *
1966 * Gets called from Console::DeleteSnapshot(), and that's basically the
1967 * only thing Console does initially. Deleting a snapshot happens entirely on
1968 * the server side if the machine is not running, and if it is running then
1969 * the individual merges are done via internal session callbacks.
1970 *
1971 * This creates a new thread that does the work and returns a progress
1972 * object to the client which is then returned to the caller of
1973 * Console::DeleteSnapshot().
1974 *
1975 * Actual work then takes place in DeleteSnapshotTask::handler().
1976 *
1977 * @note Locks mParent + this + children objects for writing!
1978 */
1979STDMETHODIMP SessionMachine::DeleteSnapshot(IConsole *aInitiator,
1980 IN_BSTR aId,
1981 MachineState_T *aMachineState,
1982 IProgress **aProgress)
1983{
1984 LogFlowThisFuncEnter();
1985
1986 Guid id(aId);
1987 AssertReturn(aInitiator && !id.isEmpty(), E_INVALIDARG);
1988 AssertReturn(aMachineState && aProgress, E_POINTER);
1989
1990 AutoCaller autoCaller(this);
1991 AssertComRCReturn(autoCaller.rc(), autoCaller.rc());
1992
1993 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1994
1995 // be very picky about machine states
1996 if ( Global::IsOnlineOrTransient(mData->mMachineState)
1997 && mData->mMachineState != MachineState_PoweredOff
1998 && mData->mMachineState != MachineState_Saved
1999 && mData->mMachineState != MachineState_Teleported
2000 && mData->mMachineState != MachineState_Aborted
2001 && mData->mMachineState != MachineState_Running
2002 && mData->mMachineState != MachineState_Paused)
2003 return setError(VBOX_E_INVALID_VM_STATE,
2004 tr("Invalid machine state: %s"),
2005 Global::stringifyMachineState(mData->mMachineState));
2006
2007 ComObjPtr<Snapshot> pSnapshot;
2008 HRESULT rc = findSnapshot(id, pSnapshot, true /* aSetError */);
2009 if (FAILED(rc)) return rc;
2010
2011 AutoWriteLock snapshotLock(pSnapshot COMMA_LOCKVAL_SRC_POS);
2012
2013 size_t childrenCount = pSnapshot->getChildrenCount();
2014 if (childrenCount > 1)
2015 return setError(VBOX_E_INVALID_OBJECT_STATE,
2016 tr("Snapshot '%s' of the machine '%ls' cannot be deleted. because it has %d child snapshots, which is more than the one snapshot allowed for deletion"),
2017 pSnapshot->getName().c_str(),
2018 mUserData->mName.raw(),
2019 childrenCount);
2020
2021 /* If the snapshot being deleted is the current one, ensure current
2022 * settings are committed and saved.
2023 */
2024 if (pSnapshot == mData->mCurrentSnapshot)
2025 {
2026 if (mData->flModifications)
2027 {
2028 rc = saveSettings(NULL);
2029 // no need to change for whether VirtualBox.xml needs saving since
2030 // we can't have a machine XML rename pending at this point
2031 if (FAILED(rc)) return rc;
2032 }
2033 }
2034
2035 ComObjPtr<SnapshotMachine> pSnapMachine = pSnapshot->getSnapshotMachine();
2036
2037 /* create a progress object. The number of operations is:
2038 * 1 (preparing) + 1 if the snapshot is online + # of normal hard disks
2039 */
2040 LogFlowThisFunc(("Going thru snapshot machine attachments to determine progress setup\n"));
2041
2042 ULONG ulOpCount = 1; // one for preparations
2043 ULONG ulTotalWeight = 1; // one for preparations
2044
2045 if (pSnapshot->stateFilePath().length())
2046 {
2047 ++ulOpCount;
2048 ++ulTotalWeight; // assume 1 MB for deleting the state file
2049 }
2050
2051 // count normal hard disks and add their sizes to the weight
2052 for (MediaData::AttachmentList::iterator it = pSnapMachine->mMediaData->mAttachments.begin();
2053 it != pSnapMachine->mMediaData->mAttachments.end();
2054 ++it)
2055 {
2056 ComObjPtr<MediumAttachment> &pAttach = *it;
2057 AutoReadLock attachLock(pAttach COMMA_LOCKVAL_SRC_POS);
2058 if (pAttach->getType() == DeviceType_HardDisk)
2059 {
2060 ComObjPtr<Medium> pHD = pAttach->getMedium();
2061 Assert(pHD);
2062 AutoReadLock mlock(pHD COMMA_LOCKVAL_SRC_POS);
2063
2064 MediumType_T type = pHD->getType();
2065 if (type != MediumType_Writethrough) // writethrough images are unaffected by snapshots, so do nothing for them
2066 {
2067 // normal or immutable media need attention
2068 ++ulOpCount;
2069 ulTotalWeight += (ULONG)(pHD->getSize() / _1M);
2070 }
2071 LogFlowThisFunc(("op %d: considering hard disk attachment %s\n", ulOpCount, pHD->getName().c_str()));
2072 }
2073 }
2074
2075 ComObjPtr<Progress> pProgress;
2076 pProgress.createObject();
2077 pProgress->init(mParent, aInitiator,
2078 BstrFmt(tr("Deleting snapshot '%s'"), pSnapshot->getName().c_str()),
2079 FALSE /* aCancelable */,
2080 ulOpCount,
2081 ulTotalWeight,
2082 Bstr(tr("Setting up")),
2083 1);
2084
2085 bool fDeleteOnline = ( (mData->mMachineState == MachineState_Running)
2086 || (mData->mMachineState == MachineState_Paused));
2087
2088 /* create and start the task on a separate thread */
2089 DeleteSnapshotTask *task = new DeleteSnapshotTask(this, pProgress,
2090 fDeleteOnline, pSnapshot);
2091 int vrc = RTThreadCreate(NULL,
2092 taskHandler,
2093 (void*)task,
2094 0,
2095 RTTHREADTYPE_MAIN_WORKER,
2096 0,
2097 "DeleteSnapshot");
2098 if (RT_FAILURE(vrc))
2099 {
2100 delete task;
2101 return E_FAIL;
2102 }
2103
2104 // the task might start running but will block on acquiring the machine's write lock
2105 // which we acquired above; once this function leaves, the task will be unblocked;
2106 // set the proper machine state here now (note: after creating a Task instance)
2107 if (mData->mMachineState == MachineState_Running)
2108 setMachineState(MachineState_DeletingSnapshotOnline);
2109 else if (mData->mMachineState == MachineState_Paused)
2110 setMachineState(MachineState_DeletingSnapshotPaused);
2111 else
2112 setMachineState(MachineState_DeletingSnapshot);
2113
2114 /* return the progress to the caller */
2115 pProgress.queryInterfaceTo(aProgress);
2116
2117 /* return the new state to the caller */
2118 *aMachineState = mData->mMachineState;
2119
2120 LogFlowThisFuncLeave();
2121
2122 return S_OK;
2123}
2124
2125/**
2126 * Helper struct for SessionMachine::deleteSnapshotHandler().
2127 */
2128struct MediumDeleteRec
2129{
2130 MediumDeleteRec()
2131 : mfNeedsOnlineMerge(false),
2132 mpMediumLockList(NULL)
2133 {}
2134
2135 MediumDeleteRec(const ComObjPtr<Medium> &aHd,
2136 const ComObjPtr<Medium> &aSource,
2137 const ComObjPtr<Medium> &aTarget,
2138 const ComObjPtr<MediumAttachment> &aOnlineMediumAttachment,
2139 bool fMergeForward,
2140 const ComObjPtr<Medium> &aParentForTarget,
2141 const MediaList &aChildrenToReparent,
2142 bool fNeedsOnlineMerge,
2143 MediumLockList *aMediumLockList)
2144 : mpHD(aHd),
2145 mpSource(aSource),
2146 mpTarget(aTarget),
2147 mpOnlineMediumAttachment(aOnlineMediumAttachment),
2148 mfMergeForward(fMergeForward),
2149 mpParentForTarget(aParentForTarget),
2150 mChildrenToReparent(aChildrenToReparent),
2151 mfNeedsOnlineMerge(fNeedsOnlineMerge),
2152 mpMediumLockList(aMediumLockList)
2153 {}
2154
2155 MediumDeleteRec(const ComObjPtr<Medium> &aHd,
2156 const ComObjPtr<Medium> &aSource,
2157 const ComObjPtr<Medium> &aTarget,
2158 const ComObjPtr<MediumAttachment> &aOnlineMediumAttachment,
2159 bool fMergeForward,
2160 const ComObjPtr<Medium> &aParentForTarget,
2161 const MediaList &aChildrenToReparent,
2162 bool fNeedsOnlineMerge,
2163 MediumLockList *aMediumLockList,
2164 const Guid &aMachineId,
2165 const Guid &aSnapshotId)
2166 : mpHD(aHd),
2167 mpSource(aSource),
2168 mpTarget(aTarget),
2169 mpOnlineMediumAttachment(aOnlineMediumAttachment),
2170 mfMergeForward(fMergeForward),
2171 mpParentForTarget(aParentForTarget),
2172 mChildrenToReparent(aChildrenToReparent),
2173 mfNeedsOnlineMerge(fNeedsOnlineMerge),
2174 mpMediumLockList(aMediumLockList),
2175 mMachineId(aMachineId),
2176 mSnapshotId(aSnapshotId)
2177 {}
2178
2179 ComObjPtr<Medium> mpHD;
2180 ComObjPtr<Medium> mpSource;
2181 ComObjPtr<Medium> mpTarget;
2182 ComObjPtr<MediumAttachment> mpOnlineMediumAttachment;
2183 bool mfMergeForward;
2184 ComObjPtr<Medium> mpParentForTarget;
2185 MediaList mChildrenToReparent;
2186 bool mfNeedsOnlineMerge;
2187 MediumLockList *mpMediumLockList;
2188 /* these are for reattaching the hard disk in case of a failure: */
2189 Guid mMachineId;
2190 Guid mSnapshotId;
2191};
2192
2193typedef std::list<MediumDeleteRec> MediumDeleteRecList;
2194
2195/**
2196 * Worker method for the delete snapshot thread created by
2197 * SessionMachine::DeleteSnapshot(). This method gets called indirectly
2198 * through SessionMachine::taskHandler() which then calls
2199 * DeleteSnapshotTask::handler().
2200 *
2201 * The DeleteSnapshotTask contains the progress object returned to the console
2202 * by SessionMachine::DeleteSnapshot, through which progress and results are
2203 * reported.
2204 *
2205 * SessionMachine::DeleteSnapshot() has set the machne state to
2206 * MachineState_DeletingSnapshot right after creating this task. Since we block
2207 * on the machine write lock at the beginning, once that has been acquired, we
2208 * can assume that the machine state is indeed that.
2209 *
2210 * @note Locks the machine + the snapshot + the media tree for writing!
2211 *
2212 * @param aTask Task data.
2213 */
2214
2215void SessionMachine::deleteSnapshotHandler(DeleteSnapshotTask &aTask)
2216{
2217 LogFlowThisFuncEnter();
2218
2219 AutoCaller autoCaller(this);
2220
2221 LogFlowThisFunc(("state=%d\n", autoCaller.state()));
2222 if (!autoCaller.isOk())
2223 {
2224 /* we might have been uninitialized because the session was accidentally
2225 * closed by the client, so don't assert */
2226 aTask.pProgress->notifyComplete(E_FAIL,
2227 COM_IIDOF(IMachine),
2228 getComponentName(),
2229 tr("The session has been accidentally closed"));
2230 LogFlowThisFuncLeave();
2231 return;
2232 }
2233
2234 MediumDeleteRecList toDelete;
2235
2236 HRESULT rc = S_OK;
2237
2238 bool fMachineSettingsChanged = false; // Machine
2239 bool fNeedsSaveSettings = false; // VirtualBox.xml
2240
2241 Guid snapshotId;
2242
2243 try
2244 {
2245 /* Locking order: */
2246 AutoMultiWriteLock3 multiLock(this->lockHandle(), // machine
2247 aTask.pSnapshot->lockHandle(), // snapshot
2248 &mParent->getMediaTreeLockHandle() // media tree
2249 COMMA_LOCKVAL_SRC_POS);
2250 // once we have this lock, we know that SessionMachine::DeleteSnapshot()
2251 // has exited after setting the machine state to MachineState_DeletingSnapshot
2252
2253 ComObjPtr<SnapshotMachine> pSnapMachine = aTask.pSnapshot->getSnapshotMachine();
2254 // no need to lock the snapshot machine since it is const by definiton
2255 Guid machineId = pSnapMachine->getId();
2256
2257 // save the snapshot ID (for callbacks)
2258 snapshotId = aTask.pSnapshot->getId();
2259
2260 // first pass:
2261 LogFlowThisFunc(("1: Checking hard disk merge prerequisites...\n"));
2262
2263 // Go thru the attachments of the snapshot machine (the media in here
2264 // point to the disk states _before_ the snapshot was taken, i.e. the
2265 // state we're restoring to; for each such medium, we will need to
2266 // merge it with its one and only child (the diff image holding the
2267 // changes written after the snapshot was taken).
2268 for (MediaData::AttachmentList::iterator it = pSnapMachine->mMediaData->mAttachments.begin();
2269 it != pSnapMachine->mMediaData->mAttachments.end();
2270 ++it)
2271 {
2272 ComObjPtr<MediumAttachment> &pAttach = *it;
2273 AutoReadLock attachLock(pAttach COMMA_LOCKVAL_SRC_POS);
2274 if (pAttach->getType() != DeviceType_HardDisk)
2275 continue;
2276
2277 ComObjPtr<Medium> pHD = pAttach->getMedium();
2278 Assert(!pHD.isNull());
2279
2280 {
2281 // writethrough images are unaffected by snapshots, skip them
2282 AutoReadLock medlock(pHD COMMA_LOCKVAL_SRC_POS);
2283 MediumType_T type = pHD->getType();
2284 if (type == MediumType_Writethrough)
2285 continue;
2286 }
2287
2288#ifdef DEBUG
2289 pHD->dumpBackRefs();
2290#endif
2291
2292 // needs to be merged with child or deleted, check prerequisites
2293 ComObjPtr<Medium> pTarget;
2294 ComObjPtr<Medium> pSource;
2295 bool fMergeForward = false;
2296 ComObjPtr<Medium> pParentForTarget;
2297 MediaList childrenToReparent;
2298 bool fNeedsOnlineMerge = false;
2299 bool fOnlineMergePossible = aTask.m_fDeleteOnline;
2300 MediumLockList *pMediumLockList = NULL;
2301 MediumLockList *pVMMALockList = NULL;
2302 ComObjPtr<MediumAttachment> pOnlineMediumAttachment;
2303 if (fOnlineMergePossible)
2304 {
2305 // Look up the corresponding medium attachment in the currently
2306 // running VM. Any failure prevents a live merge. Could be made
2307 // a tad smarter by trying a few candidates, so that e.g. disks
2308 // which are simply moved to a different controller slot do not
2309 // prevent online merging in general.
2310 pOnlineMediumAttachment =
2311 findAttachment(mMediaData->mAttachments,
2312 pAttach->getControllerName(),
2313 pAttach->getPort(),
2314 pAttach->getDevice());
2315 if (pOnlineMediumAttachment)
2316 {
2317 rc = mData->mSession.mLockedMedia.Get(pOnlineMediumAttachment,
2318 pVMMALockList);
2319 if (FAILED(rc))
2320 fOnlineMergePossible = false;
2321 }
2322 else
2323 fOnlineMergePossible = false;
2324 }
2325 rc = prepareDeleteSnapshotMedium(pHD, machineId, snapshotId,
2326 fOnlineMergePossible,
2327 pVMMALockList, pSource, pTarget,
2328 fMergeForward, pParentForTarget,
2329 childrenToReparent,
2330 fNeedsOnlineMerge,
2331 pMediumLockList);
2332 if (FAILED(rc))
2333 throw rc;
2334
2335 // no need to hold the lock any longer
2336 attachLock.release();
2337
2338 // For simplicity, prepareDeleteSnapshotMedium selects the merge
2339 // direction in the following way: we merge pHD onto its child
2340 // (forward merge), not the other way round, because that saves us
2341 // from unnecessarily shuffling around the attachments for the
2342 // machine that follows the snapshot (next snapshot or current
2343 // state), unless it's a base image. Backwards merges of the first
2344 // snapshot into the base image is essential, as it ensures that
2345 // when all snapshots are deleted the only remaining image is a
2346 // base image. Important e.g. for medium formats which do not have
2347 // a file representation such as iSCSI.
2348
2349 // a couple paranoia checks for backward merges
2350 if (pMediumLockList != NULL && !fMergeForward)
2351 {
2352 // parent is null -> this disk is a base hard disk: we will
2353 // then do a backward merge, i.e. merge its only child onto the
2354 // base disk. Here we need then to update the attachment that
2355 // refers to the child and have it point to the parent instead
2356 Assert(pHD->getParent().isNull());
2357 Assert(pHD->getChildren().size() == 1);
2358
2359 ComObjPtr<Medium> pReplaceHD = pHD->getChildren().front();
2360
2361 ComAssertThrow(pReplaceHD == pSource, E_FAIL);
2362 }
2363
2364 Guid replaceMachineId;
2365 Guid replaceSnapshotId;
2366
2367 const Guid *pReplaceMachineId = pSource->getFirstMachineBackrefId();
2368 // minimal sanity checking
2369 Assert(!pReplaceMachineId || *pReplaceMachineId == mData->mUuid);
2370 if (pReplaceMachineId)
2371 replaceMachineId = *pReplaceMachineId;
2372
2373 const Guid *pSnapshotId = pSource->getFirstMachineBackrefSnapshotId();
2374 if (pSnapshotId)
2375 replaceSnapshotId = *pSnapshotId;
2376
2377 if (!replaceMachineId.isEmpty())
2378 {
2379 // Adjust the backreferences, otherwise merging will assert.
2380 // Note that the medium attachment object stays associated
2381 // with the snapshot until the merge was successful.
2382 HRESULT rc2 = S_OK;
2383 rc2 = pSource->detachFrom(replaceMachineId, replaceSnapshotId);
2384 AssertComRC(rc2);
2385
2386 toDelete.push_back(MediumDeleteRec(pHD, pSource, pTarget,
2387 pOnlineMediumAttachment,
2388 fMergeForward,
2389 pParentForTarget,
2390 childrenToReparent,
2391 fNeedsOnlineMerge,
2392 pMediumLockList,
2393 replaceMachineId,
2394 replaceSnapshotId));
2395 }
2396 else
2397 toDelete.push_back(MediumDeleteRec(pHD, pSource, pTarget,
2398 pOnlineMediumAttachment,
2399 fMergeForward,
2400 pParentForTarget,
2401 childrenToReparent,
2402 fNeedsOnlineMerge,
2403 pMediumLockList));
2404 }
2405
2406 // we can release the lock now since the machine state is MachineState_DeletingSnapshot
2407 multiLock.release();
2408
2409 /* Now we checked that we can successfully merge all normal hard disks
2410 * (unless a runtime error like end-of-disc happens). Now get rid of
2411 * the saved state (if present), as that will free some disk space.
2412 * The snapshot itself will be deleted as late as possible, so that
2413 * the user can repeat the delete operation if he runs out of disk
2414 * space or cancels the delete operation. */
2415
2416 /* second pass: */
2417 LogFlowThisFunc(("2: Deleting saved state...\n"));
2418
2419 {
2420 // saveAllSnapshots() needs a machine lock, and the snapshots
2421 // tree is protected by the machine lock as well
2422 AutoWriteLock machineLock(this COMMA_LOCKVAL_SRC_POS);
2423
2424 Utf8Str stateFilePath = aTask.pSnapshot->stateFilePath();
2425 if (!stateFilePath.isEmpty())
2426 {
2427 aTask.pProgress->SetNextOperation(Bstr(tr("Deleting the execution state")),
2428 1); // weight
2429
2430 aTask.pSnapshot->deleteStateFile();
2431 fMachineSettingsChanged = true;
2432 }
2433 }
2434
2435 /* third pass: */
2436 LogFlowThisFunc(("3: Performing actual hard disk merging...\n"));
2437
2438 /// @todo NEWMEDIA turn the following errors into warnings because the
2439 /// snapshot itself has been already deleted (and interpret these
2440 /// warnings properly on the GUI side)
2441 for (MediumDeleteRecList::iterator it = toDelete.begin();
2442 it != toDelete.end();)
2443 {
2444 const ComObjPtr<Medium> &pMedium(it->mpHD);
2445 ULONG ulWeight;
2446
2447 {
2448 AutoReadLock alock(pMedium COMMA_LOCKVAL_SRC_POS);
2449 ulWeight = (ULONG)(pMedium->getSize() / _1M);
2450 }
2451
2452 aTask.pProgress->SetNextOperation(BstrFmt(tr("Merging differencing image '%s'"),
2453 pMedium->getName().raw()),
2454 ulWeight);
2455
2456 bool fNeedSourceUninit = false;
2457 bool fReparentTarget = false;
2458 if (it->mpMediumLockList == NULL)
2459 {
2460 /* no real merge needed, just updating state and delete
2461 * diff files if necessary */
2462 AutoMultiWriteLock2 mLock(&mParent->getMediaTreeLockHandle(), pMedium->lockHandle() COMMA_LOCKVAL_SRC_POS);
2463
2464 Assert( !it->mfMergeForward
2465 || pMedium->getChildren().size() == 0);
2466
2467 /* Delete the differencing hard disk (has no children). Two
2468 * exceptions: if it's the last medium in the chain or if it's
2469 * a backward merge we don't want to handle due to complextity.
2470 * In both cases leave the image in place. If it's the first
2471 * exception the user can delete it later if he wants. */
2472 if (!pMedium->getParent().isNull())
2473 {
2474 Assert(pMedium->getState() == MediumState_Deleting);
2475 /* No need to hold the lock any longer. */
2476 mLock.release();
2477 bool fNeedsSave = false;
2478 rc = pMedium->deleteStorage(&aTask.pProgress,
2479 true /* aWait */,
2480 &fNeedsSave);
2481 fNeedsSaveSettings |= fNeedsSave;
2482 if (FAILED(rc))
2483 throw rc;
2484
2485 // need to uninit the deleted medium
2486 fNeedSourceUninit = true;
2487 }
2488 }
2489 else
2490 {
2491 bool fNeedsSave = false;
2492 if (it->mfNeedsOnlineMerge)
2493 {
2494/// @todo VBoxHDD cannot handle backward merges where source==active disk yet
2495 if (!it->mfMergeForward && it->mChildrenToReparent.size() == 0)
2496 throw setError(E_NOTIMPL,
2497 tr("Snapshot '%s' of the machine '%ls' cannot be deleted while a VM is running, as this case is not implemented yet. You can delete the snapshot when the VM is powered off"),
2498 aTask.pSnapshot->getName().c_str(),
2499 mUserData->mName.raw());
2500
2501 // online medium merge, in the direction decided earlier
2502 rc = onlineMergeMedium(it->mpOnlineMediumAttachment,
2503 it->mpSource,
2504 it->mpTarget,
2505 it->mfMergeForward,
2506 it->mpParentForTarget,
2507 it->mChildrenToReparent,
2508 it->mpMediumLockList,
2509 aTask.pProgress,
2510 &fNeedsSave);
2511 }
2512 else
2513 {
2514 // normal medium merge, in the direction decided earlier
2515 rc = it->mpSource->mergeTo(it->mpTarget,
2516 it->mfMergeForward,
2517 it->mpParentForTarget,
2518 it->mChildrenToReparent,
2519 it->mpMediumLockList,
2520 &aTask.pProgress,
2521 true /* aWait */,
2522 &fNeedsSave);
2523 }
2524 fNeedsSaveSettings |= fNeedsSave;
2525
2526 // If the merge failed, we need to do our best to have a usable
2527 // VM configuration afterwards. The return code doesn't tell
2528 // whether the merge completed and so we have to check if the
2529 // source medium (diff images are always file based at the
2530 // moment) is still there or not. Be careful not to lose the
2531 // error code below, before the "Delayed failure exit".
2532 if (FAILED(rc))
2533 {
2534 AutoReadLock mlock(it->mpSource COMMA_LOCKVAL_SRC_POS);
2535 const ComObjPtr<MediumFormat> &sourceFormat = it->mpSource->getMediumFormat();
2536 // No medium format description? get out of here.
2537 if (sourceFormat.isNull())
2538 throw rc;
2539 // Diff medium not backed by a file - cannot get status so
2540 // be pessimistic.
2541 if (!(sourceFormat->capabilities() & MediumFormatCapabilities_File))
2542 throw rc;
2543 const Utf8Str &loc = it->mpSource->getLocationFull();
2544 // Source medium is still there, so merge failed early.
2545 if (RTFileExists(loc.raw()))
2546 throw rc;
2547
2548 // Source medium is gone. Assume the merge succeeded and
2549 // thus it's safe to remove the attachment. We use the
2550 // "Delayed failure exit" below.
2551 }
2552
2553 // need to change the medium attachment for backward merges
2554 fReparentTarget = !it->mfMergeForward;
2555
2556 if (!it->mfNeedsOnlineMerge)
2557 {
2558 // need to uninit the medium deleted by the merge
2559 fNeedSourceUninit = true;
2560
2561 // delete the no longer needed medium lock list, which
2562 // implicitly handled the unlocking
2563 delete it->mpMediumLockList;
2564 it->mpMediumLockList = NULL;
2565 }
2566 }
2567
2568 // Now that the medium is successfully merged/deleted/whatever,
2569 // remove the medium attachment from the snapshot. For a backwards
2570 // merge the target attachment needs to be removed from the
2571 // snapshot, as the VM will take it over. For forward merges the
2572 // source medium attachment needs to be removed.
2573 ComObjPtr<MediumAttachment> pAtt;
2574 if (fReparentTarget)
2575 {
2576 pAtt = findAttachment(pSnapMachine->mMediaData->mAttachments,
2577 it->mpTarget);
2578 it->mpTarget->detachFrom(machineId, snapshotId);
2579 }
2580 else
2581 pAtt = findAttachment(pSnapMachine->mMediaData->mAttachments,
2582 it->mpSource);
2583 pSnapMachine->mMediaData->mAttachments.remove(pAtt);
2584
2585 if (fReparentTarget)
2586 {
2587 // Search for old source attachment and replace with target.
2588 // There can be only one child snapshot in this case.
2589 ComObjPtr<Machine> pMachine = this;
2590 Guid childSnapshotId;
2591 ComObjPtr<Snapshot> pChildSnapshot = aTask.pSnapshot->getFirstChild();
2592 if (pChildSnapshot)
2593 {
2594 pMachine = pChildSnapshot->getSnapshotMachine();
2595 childSnapshotId = pChildSnapshot->getId();
2596 }
2597 pAtt = findAttachment(pMachine->mMediaData->mAttachments, it->mpSource);
2598 // If no attachment is found do not change anything. The source
2599 // medium might not have been attached to the snapshot.
2600 if (pAtt)
2601 {
2602 AutoWriteLock attLock(pAtt COMMA_LOCKVAL_SRC_POS);
2603 pAtt->updateMedium(it->mpTarget, false /* aImplicit */);
2604 it->mpTarget->attachTo(pMachine->mData->mUuid, childSnapshotId);
2605 }
2606 }
2607
2608 if (fNeedSourceUninit)
2609 it->mpSource->uninit();
2610
2611 // One attachment is merged, must save the settings
2612 fMachineSettingsChanged = true;
2613
2614 // prevent calling cancelDeleteSnapshotMedium() for this attachment
2615 it = toDelete.erase(it);
2616
2617 // Delayed failure exit when the merge cleanup failed but the
2618 // merge actually succeeded.
2619 if (FAILED(rc))
2620 throw rc;
2621 }
2622
2623 {
2624 // beginSnapshotDelete() needs the machine lock, and the snapshots
2625 // tree is protected by the machine lock as well
2626 AutoWriteLock machineLock(this COMMA_LOCKVAL_SRC_POS);
2627
2628 aTask.pSnapshot->beginSnapshotDelete();
2629 aTask.pSnapshot->uninit();
2630
2631 fMachineSettingsChanged = true;
2632 }
2633 }
2634 catch (HRESULT aRC) { rc = aRC; }
2635
2636 if (FAILED(rc))
2637 {
2638 // preserve existing error info so that the result can
2639 // be properly reported to the progress object below
2640 ErrorInfoKeeper eik;
2641
2642 AutoMultiWriteLock2 multiLock(this->lockHandle(), // machine
2643 &mParent->getMediaTreeLockHandle() // media tree
2644 COMMA_LOCKVAL_SRC_POS);
2645
2646 // un-prepare the remaining hard disks
2647 for (MediumDeleteRecList::const_iterator it = toDelete.begin();
2648 it != toDelete.end();
2649 ++it)
2650 {
2651 cancelDeleteSnapshotMedium(it->mpHD, it->mpSource,
2652 it->mChildrenToReparent,
2653 it->mfNeedsOnlineMerge,
2654 it->mpMediumLockList, it->mMachineId,
2655 it->mSnapshotId);
2656 }
2657 }
2658
2659 // whether we were successful or not, we need to set the machine
2660 // state and save the machine settings;
2661 {
2662 // preserve existing error info so that the result can
2663 // be properly reported to the progress object below
2664 ErrorInfoKeeper eik;
2665
2666 // restore the machine state that was saved when the
2667 // task was started
2668 setMachineState(aTask.machineStateBackup);
2669 updateMachineStateOnClient();
2670
2671 if (fMachineSettingsChanged || fNeedsSaveSettings)
2672 {
2673 if (fMachineSettingsChanged)
2674 {
2675 AutoWriteLock machineLock(this COMMA_LOCKVAL_SRC_POS);
2676 saveSettings(&fNeedsSaveSettings, SaveS_InformCallbacksAnyway);
2677 }
2678
2679 if (fNeedsSaveSettings)
2680 {
2681 AutoWriteLock vboxLock(mParent COMMA_LOCKVAL_SRC_POS);
2682 mParent->saveSettings();
2683 }
2684 }
2685 }
2686
2687 // report the result (this will try to fetch current error info on failure)
2688 aTask.pProgress->notifyComplete(rc);
2689
2690 if (SUCCEEDED(rc))
2691 mParent->onSnapshotDeleted(mData->mUuid, snapshotId);
2692
2693 LogFlowThisFunc(("Done deleting snapshot (rc=%08X)\n", rc));
2694 LogFlowThisFuncLeave();
2695}
2696
2697/**
2698 * Checks that this hard disk (part of a snapshot) may be deleted/merged and
2699 * performs necessary state changes. Must not be called for writethrough disks
2700 * because there is nothing to delete/merge then.
2701 *
2702 * This method is to be called prior to calling #deleteSnapshotMedium().
2703 * If #deleteSnapshotMedium() is not called or fails, the state modifications
2704 * performed by this method must be undone by #cancelDeleteSnapshotMedium().
2705 *
2706 * @return COM status code
2707 * @param aHD Hard disk which is connected to the snapshot.
2708 * @param aMachineId UUID of machine this hard disk is attached to.
2709 * @param aSnapshotId UUID of snapshot this hard disk is attached to. May
2710 * be a zero UUID if no snapshot is applicable.
2711 * @param fOnlineMergePossible Flag whether an online merge is possible.
2712 * @param aVMMALockList Medium lock list for the medium attachment of this VM.
2713 * Only used if @a fOnlineMergePossible is @c true, and
2714 * must be non-NULL in this case.
2715 * @param aSource Source hard disk for merge (out).
2716 * @param aTarget Target hard disk for merge (out).
2717 * @param aMergeForward Merge direction decision (out).
2718 * @param aParentForTarget New parent if target needs to be reparented (out).
2719 * @param aChildrenToReparent Children which have to be reparented to the
2720 * target (out).
2721 * @param fNeedsOnlineMerge Whether this merge needs to be done online (out).
2722 * If this is set to @a true then the @a aVMMALockList
2723 * parameter has been modified and is returned as
2724 * @a aMediumLockList.
2725 * @param aMediumLockList Where to store the created medium lock list (may
2726 * return NULL if no real merge is necessary).
2727 *
2728 * @note Caller must hold media tree lock for writing. This locks this object
2729 * and every medium object on the merge chain for writing.
2730 */
2731HRESULT SessionMachine::prepareDeleteSnapshotMedium(const ComObjPtr<Medium> &aHD,
2732 const Guid &aMachineId,
2733 const Guid &aSnapshotId,
2734 bool fOnlineMergePossible,
2735 MediumLockList *aVMMALockList,
2736 ComObjPtr<Medium> &aSource,
2737 ComObjPtr<Medium> &aTarget,
2738 bool &aMergeForward,
2739 ComObjPtr<Medium> &aParentForTarget,
2740 MediaList &aChildrenToReparent,
2741 bool &fNeedsOnlineMerge,
2742 MediumLockList * &aMediumLockList)
2743{
2744 Assert(mParent->getMediaTreeLockHandle().isWriteLockOnCurrentThread());
2745 Assert(!fOnlineMergePossible || VALID_PTR(aVMMALockList));
2746
2747 AutoWriteLock alock(aHD COMMA_LOCKVAL_SRC_POS);
2748
2749 // Medium must not be writethrough at this point
2750 AssertReturn(aHD->getType() != MediumType_Writethrough, E_FAIL);
2751
2752 aMediumLockList = NULL;
2753 fNeedsOnlineMerge = false;
2754
2755 if (aHD->getChildren().size() == 0)
2756 {
2757 /* This technically is no merge, set those values nevertheless.
2758 * Helps with updating the medium attachments. */
2759 aSource = aHD;
2760 aTarget = aHD;
2761
2762 /* special treatment of the last hard disk in the chain: */
2763 if (aHD->getParent().isNull())
2764 {
2765 /* lock only, to prevent any usage until the snapshot deletion
2766 * is completed */
2767 return aHD->LockWrite(NULL);
2768 }
2769
2770 /* the differencing hard disk w/o children will be deleted, protect it
2771 * from attaching to other VMs (this is why Deleting) */
2772 return aHD->markForDeletion();
2773 }
2774
2775 /* not going multi-merge as it's too expensive */
2776 if (aHD->getChildren().size() > 1)
2777 return setError(E_FAIL,
2778 tr("Hard disk '%s' has more than one child hard disk (%d)"),
2779 aHD->getLocationFull().raw(),
2780 aHD->getChildren().size());
2781
2782 ComObjPtr<Medium> pChild = aHD->getChildren().front();
2783
2784 /* we keep this locked, so lock the affected child to make sure the lock
2785 * order is correct when calling prepareMergeTo() */
2786 AutoWriteLock childLock(pChild COMMA_LOCKVAL_SRC_POS);
2787
2788 /* the rest is a normal merge setup */
2789 if (aHD->getParent().isNull())
2790 {
2791 /* base hard disk, backward merge */
2792 const Guid *pMachineId1 = pChild->getFirstMachineBackrefId();
2793 const Guid *pMachineId2 = aHD->getFirstMachineBackrefId();
2794 if (pMachineId1 && pMachineId2 && *pMachineId1 != *pMachineId2)
2795 {
2796 /* backward merge is too tricky, we'll just detach on snapshot
2797 * deletion, so lock only, to prevent any usage */
2798 return aHD->LockWrite(NULL);
2799 }
2800
2801 aSource = pChild;
2802 aTarget = aHD;
2803 }
2804 else
2805 {
2806 /* forward merge */
2807 aSource = aHD;
2808 aTarget = pChild;
2809 }
2810
2811 HRESULT rc;
2812 rc = aSource->prepareMergeTo(aTarget, &aMachineId, &aSnapshotId,
2813 !fOnlineMergePossible /* fLockMedia */,
2814 aMergeForward, aParentForTarget,
2815 aChildrenToReparent, aMediumLockList);
2816 if (SUCCEEDED(rc) && fOnlineMergePossible)
2817 {
2818 /* Try to lock the newly constructed medium lock list. If it succeeds
2819 * this can be handled as an offline merge, i.e. without the need of
2820 * asking the VM to do the merging. Only continue with the online
2821 * merging preparation if applicable. */
2822 rc = aMediumLockList->Lock();
2823 if (FAILED(rc) && fOnlineMergePossible)
2824 {
2825 /* Locking failed, this cannot be done as an offline merge. Try to
2826 * combine the locking information into the lock list of the medium
2827 * attachment in the running VM. If that fails or locking the
2828 * resulting lock list fails then the merge cannot be done online.
2829 * It can be repeated by the user when the VM is shut down. */
2830 MediumLockList::Base::iterator lockListVMMABegin =
2831 aVMMALockList->GetBegin();
2832 MediumLockList::Base::iterator lockListVMMAEnd =
2833 aVMMALockList->GetEnd();
2834 MediumLockList::Base::iterator lockListBegin =
2835 aMediumLockList->GetBegin();
2836 MediumLockList::Base::iterator lockListEnd =
2837 aMediumLockList->GetEnd();
2838 for (MediumLockList::Base::iterator it = lockListVMMABegin,
2839 it2 = lockListBegin;
2840 it2 != lockListEnd;
2841 ++it, ++it2)
2842 {
2843 if ( it == lockListVMMAEnd
2844 || it->GetMedium() != it2->GetMedium())
2845 {
2846 fOnlineMergePossible = false;
2847 break;
2848 }
2849 bool fLockReq = (it2->GetLockRequest() || it->GetLockRequest());
2850 rc = it->UpdateLock(fLockReq);
2851 if (FAILED(rc))
2852 {
2853 // could not update the lock, trigger cleanup below
2854 fOnlineMergePossible = false;
2855 break;
2856 }
2857 }
2858
2859 if (fOnlineMergePossible)
2860 {
2861 /* we will lock the children of the source for reparenting */
2862 for (MediaList::const_iterator it = aChildrenToReparent.begin();
2863 it != aChildrenToReparent.end();
2864 ++it)
2865 {
2866 ComObjPtr<Medium> pMedium = *it;
2867 if (pMedium->getState() == MediumState_Created)
2868 {
2869 rc = pMedium->LockWrite(NULL);
2870 if (FAILED(rc))
2871 throw rc;
2872 }
2873 else
2874 {
2875 rc = aVMMALockList->Update(pMedium, true);
2876 if (FAILED(rc))
2877 {
2878 rc = pMedium->LockWrite(NULL);
2879 if (FAILED(rc))
2880 throw rc;
2881 }
2882 }
2883 }
2884 }
2885
2886 if (fOnlineMergePossible)
2887 {
2888 rc = aVMMALockList->Lock();
2889 if (FAILED(rc))
2890 {
2891 aSource->cancelMergeTo(aChildrenToReparent, aMediumLockList);
2892 rc = setError(rc,
2893 tr("Cannot lock hard disk '%s' for a live merge"),
2894 aHD->getLocationFull().raw());
2895 }
2896 else
2897 {
2898 delete aMediumLockList;
2899 aMediumLockList = aVMMALockList;
2900 fNeedsOnlineMerge = true;
2901 }
2902 }
2903 else
2904 {
2905 aSource->cancelMergeTo(aChildrenToReparent, aMediumLockList);
2906 rc = setError(rc,
2907 tr("Failed to construct lock list for a live merge of hard disk '%s'"),
2908 aHD->getLocationFull().raw());
2909 }
2910
2911 // fix the VM's lock list if anything failed
2912 if (FAILED(rc))
2913 {
2914 lockListVMMABegin = aVMMALockList->GetBegin();
2915 lockListVMMAEnd = aVMMALockList->GetEnd();
2916 MediumLockList::Base::iterator lockListLast = lockListVMMAEnd;
2917 lockListLast--;
2918 for (MediumLockList::Base::iterator it = lockListVMMABegin;
2919 it != lockListVMMAEnd;
2920 ++it)
2921 {
2922 it->UpdateLock(it == lockListLast);
2923 ComObjPtr<Medium> pMedium = it->GetMedium();
2924 AutoWriteLock mediumLock(pMedium COMMA_LOCKVAL_SRC_POS);
2925 // blindly apply this, only needed for medium objects which
2926 // would be deleted as part of the merge
2927 pMedium->unmarkLockedForDeletion();
2928 }
2929 }
2930
2931 }
2932 else
2933 {
2934 aSource->cancelMergeTo(aChildrenToReparent, aMediumLockList);
2935 rc = setError(rc,
2936 tr("Cannot lock hard disk '%s' for an offline merge"),
2937 aHD->getLocationFull().raw());
2938 }
2939 }
2940
2941 return rc;
2942}
2943
2944/**
2945 * Cancels the deletion/merging of this hard disk (part of a snapshot). Undoes
2946 * what #prepareDeleteSnapshotMedium() did. Must be called if
2947 * #deleteSnapshotMedium() is not called or fails.
2948 *
2949 * @param aHD Hard disk which is connected to the snapshot.
2950 * @param aSource Source hard disk for merge.
2951 * @param aChildrenToReparent Children to unlock.
2952 * @param fNeedsOnlineMerge Whether this merge needs to be done online.
2953 * @param aMediumLockList Medium locks to cancel.
2954 * @param aMachineId Machine id to attach the medium to.
2955 * @param aSnapshotId Snapshot id to attach the medium to.
2956 *
2957 * @note Locks the medium tree and the hard disks in the chain for writing.
2958 */
2959void SessionMachine::cancelDeleteSnapshotMedium(const ComObjPtr<Medium> &aHD,
2960 const ComObjPtr<Medium> &aSource,
2961 const MediaList &aChildrenToReparent,
2962 bool fNeedsOnlineMerge,
2963 MediumLockList *aMediumLockList,
2964 const Guid &aMachineId,
2965 const Guid &aSnapshotId)
2966{
2967 if (aMediumLockList == NULL)
2968 {
2969 AutoMultiWriteLock2 mLock(&mParent->getMediaTreeLockHandle(), aHD->lockHandle() COMMA_LOCKVAL_SRC_POS);
2970
2971 Assert(aHD->getChildren().size() == 0);
2972
2973 if (aHD->getParent().isNull())
2974 {
2975 HRESULT rc = aHD->UnlockWrite(NULL);;
2976 AssertComRC(rc);
2977 }
2978 else
2979 {
2980 HRESULT rc = aHD->unmarkForDeletion();
2981 AssertComRC(rc);
2982 }
2983 }
2984 else
2985 {
2986 if (fNeedsOnlineMerge)
2987 {
2988 // Online merge uses the medium lock list of the VM, so give
2989 // an empty list to cancelMergeTo so that it works as designed.
2990 aSource->cancelMergeTo(aChildrenToReparent, new MediumLockList());
2991
2992 // clean up the VM medium lock list ourselves
2993 MediumLockList::Base::iterator lockListBegin =
2994 aMediumLockList->GetBegin();
2995 MediumLockList::Base::iterator lockListEnd =
2996 aMediumLockList->GetEnd();
2997 MediumLockList::Base::iterator lockListLast = lockListEnd;
2998 lockListLast--;
2999 for (MediumLockList::Base::iterator it = lockListBegin;
3000 it != lockListEnd;
3001 ++it)
3002 {
3003 ComObjPtr<Medium> pMedium = it->GetMedium();
3004 AutoWriteLock mediumLock(pMedium COMMA_LOCKVAL_SRC_POS);
3005 if (pMedium->getState() == MediumState_Deleting)
3006 pMedium->unmarkForDeletion();
3007 else
3008 {
3009 // blindly apply this, only needed for medium objects which
3010 // would be deleted as part of the merge
3011 pMedium->unmarkLockedForDeletion();
3012 }
3013 it->UpdateLock(it == lockListLast);
3014 }
3015 }
3016 else
3017 {
3018 aSource->cancelMergeTo(aChildrenToReparent, aMediumLockList);
3019 }
3020 }
3021
3022 if (!aMachineId.isEmpty())
3023 {
3024 // reattach the source media to the snapshot
3025 HRESULT rc = aSource->attachTo(aMachineId, aSnapshotId);
3026 AssertComRC(rc);
3027 }
3028}
3029
3030/**
3031 * Perform an online merge of a hard disk, i.e. the equivalent of
3032 * Medium::mergeTo(), just for running VMs. If this fails you need to call
3033 * #cancelDeleteSnapshotMedium().
3034 *
3035 * @return COM status code
3036 * @param aMediumAttachment Identify where the disk is attached in the VM.
3037 * @param aSource Source hard disk for merge.
3038 * @param aTarget Target hard disk for merge.
3039 * @param aMergeForward Merge direction.
3040 * @param aParentForTarget New parent if target needs to be reparented.
3041 * @param aChildrenToReparent Children which have to be reparented to the
3042 * target.
3043 * @param aMediumLockList Where to store the created medium lock list (may
3044 * return NULL if no real merge is necessary).
3045 * @param aProgress Progress indicator.
3046 * @param pfNeedsSaveSettings Whether the VM settings need to be saved (out).
3047 */
3048HRESULT SessionMachine::onlineMergeMedium(const ComObjPtr<MediumAttachment> &aMediumAttachment,
3049 const ComObjPtr<Medium> &aSource,
3050 const ComObjPtr<Medium> &aTarget,
3051 bool fMergeForward,
3052 const ComObjPtr<Medium> &aParentForTarget,
3053 const MediaList &aChildrenToReparent,
3054 MediumLockList *aMediumLockList,
3055 ComObjPtr<Progress> &aProgress,
3056 bool *pfNeedsSaveSettings)
3057{
3058 AssertReturn(aSource != NULL, E_FAIL);
3059 AssertReturn(aTarget != NULL, E_FAIL);
3060 AssertReturn(aSource != aTarget, E_FAIL);
3061 AssertReturn(aMediumLockList != NULL, E_FAIL);
3062
3063 HRESULT rc = S_OK;
3064
3065 try
3066 {
3067 // Similar code appears in Medium::taskMergeHandle, so
3068 // if you make any changes below check whether they are applicable
3069 // in that context as well.
3070
3071 unsigned uTargetIdx = (unsigned)-1;
3072 unsigned uSourceIdx = (unsigned)-1;
3073 /* Sanity check all hard disks in the chain. */
3074 MediumLockList::Base::iterator lockListBegin =
3075 aMediumLockList->GetBegin();
3076 MediumLockList::Base::iterator lockListEnd =
3077 aMediumLockList->GetEnd();
3078 unsigned i = 0;
3079 for (MediumLockList::Base::iterator it = lockListBegin;
3080 it != lockListEnd;
3081 ++it)
3082 {
3083 MediumLock &mediumLock = *it;
3084 const ComObjPtr<Medium> &pMedium = mediumLock.GetMedium();
3085
3086 if (pMedium == aSource)
3087 uSourceIdx = i;
3088 else if (pMedium == aTarget)
3089 uTargetIdx = i;
3090
3091 // In Medium::taskMergeHandler there is lots of consistency
3092 // checking which we cannot do here, as the state details are
3093 // impossible to get outside the Medium class. The locking should
3094 // have done the checks already.
3095
3096 i++;
3097 }
3098
3099 ComAssertThrow( uSourceIdx != (unsigned)-1
3100 && uTargetIdx != (unsigned)-1, E_FAIL);
3101
3102 // For forward merges, tell the VM what images need to have their
3103 // parent UUID updated. This cannot be done in VBoxSVC, as opening
3104 // the required parent images is not safe while the VM is running.
3105 // For backward merges this will be simply an array of size 0.
3106 com::SafeIfaceArray<IMedium> childrenToReparent(aChildrenToReparent);
3107
3108 ComPtr<IInternalSessionControl> directControl;
3109 {
3110 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3111
3112 if (mData->mSession.mState != SessionState_Open)
3113 throw setError(VBOX_E_INVALID_VM_STATE,
3114 tr("Machine session is not open (session state: %s)"),
3115 Global::stringifySessionState(mData->mSession.mState));
3116 directControl = mData->mSession.mDirectControl;
3117 }
3118
3119 // Must not hold any locks here, as this will call back to finish
3120 // updating the medium attachment, chain linking and state.
3121 rc = directControl->OnlineMergeMedium(aMediumAttachment,
3122 uSourceIdx, uTargetIdx,
3123 aSource, aTarget,
3124 fMergeForward, aParentForTarget,
3125 ComSafeArrayAsInParam(childrenToReparent),
3126 aProgress);
3127 if (FAILED(rc))
3128 throw rc;
3129 }
3130 catch (HRESULT aRC) { rc = aRC; }
3131
3132 // The callback mentioned above takes care of update the medium state
3133
3134 if (pfNeedsSaveSettings)
3135 *pfNeedsSaveSettings = true;
3136
3137 return rc;
3138}
3139
3140/**
3141 * Implementation for IInternalMachineControl::finishOnlineMergeMedium().
3142 *
3143 * Gets called after the successful completion of an online merge from
3144 * Console::onlineMergeMedium(), which gets invoked indirectly above in
3145 * the call to IInternalSessionControl::onlineMergeMedium.
3146 *
3147 * This updates the medium information and medium state so that the VM
3148 * can continue with the updated state of the medium chain.
3149 */
3150STDMETHODIMP SessionMachine::FinishOnlineMergeMedium(IMediumAttachment *aMediumAttachment,
3151 IMedium *aSource,
3152 IMedium *aTarget,
3153 BOOL aMergeForward,
3154 IMedium *aParentForTarget,
3155 ComSafeArrayIn(IMedium *, aChildrenToReparent))
3156{
3157 HRESULT rc = S_OK;
3158 ComObjPtr<Medium> pSource(static_cast<Medium *>(aSource));
3159 ComObjPtr<Medium> pTarget(static_cast<Medium *>(aTarget));
3160 ComObjPtr<Medium> pParentForTarget(static_cast<Medium *>(aParentForTarget));
3161
3162 // all hard disks but the target were successfully deleted by
3163 // the merge; reparent target if necessary and uninitialize media
3164
3165 AutoWriteLock treeLock(mParent->getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
3166
3167 if (aMergeForward)
3168 {
3169 // first, unregister the target since it may become a base
3170 // hard disk which needs re-registration
3171 rc = mParent->unregisterHardDisk(pTarget, NULL /*&fNeedsSaveSettings*/);
3172 AssertComRC(rc);
3173
3174 // then, reparent it and disconnect the deleted branch at
3175 // both ends (chain->parent() is source's parent)
3176 pTarget->deparent();
3177 pTarget->setParent(pParentForTarget);
3178 if (pParentForTarget)
3179 pSource->deparent();
3180
3181 // then, register again
3182 rc = mParent->registerHardDisk(pTarget, NULL /*&fNeedsSaveSettings*/);
3183 AssertComRC(rc);
3184 }
3185 else
3186 {
3187 Assert(pTarget->getChildren().size() == 1);
3188 Medium *targetChild = pTarget->getChildren().front();
3189
3190 // disconnect the deleted branch at the elder end
3191 targetChild->deparent();
3192
3193 // Update parent UUIDs of the source's children, reparent them and
3194 // disconnect the deleted branch at the younger end
3195 com::SafeIfaceArray<IMedium> childrenToReparent(ComSafeArrayInArg(aChildrenToReparent));
3196 if (childrenToReparent.size() > 0)
3197 {
3198 // Fix the parent UUID of the images which needs to be moved to
3199 // underneath target. The running machine has the images opened,
3200 // but only for reading since the VM is paused. If anything fails
3201 // we must continue. The worst possible result is that the images
3202 // need manual fixing via VBoxManage to adjust the parent UUID.
3203 MediaList toReparent;
3204 for (size_t i = 0; i < childrenToReparent.size(); i++)
3205 {
3206 Medium *pMedium = static_cast<Medium *>(childrenToReparent[i]);
3207 toReparent.push_back(pMedium);
3208 }
3209 pTarget->fixParentUuidOfChildren(toReparent);
3210
3211 // obey {parent,child} lock order
3212 AutoWriteLock sourceLock(pSource COMMA_LOCKVAL_SRC_POS);
3213
3214 for (size_t i = 0; i < childrenToReparent.size(); i++)
3215 {
3216 Medium *pMedium = static_cast<Medium *>(childrenToReparent[i]);
3217 AutoWriteLock childLock(pMedium COMMA_LOCKVAL_SRC_POS);
3218
3219 pMedium->deparent(); // removes pMedium from source
3220 pMedium->setParent(pTarget);
3221 }
3222 }
3223 }
3224
3225 /* unregister and uninitialize all hard disks removed by the merge */
3226 MediumLockList *pMediumLockList = NULL;
3227 rc = mData->mSession.mLockedMedia.Get(static_cast<MediumAttachment *>(aMediumAttachment),
3228 pMediumLockList);
3229 const ComObjPtr<Medium> &pLast = aMergeForward ? pTarget : pSource;
3230 AssertReturn(SUCCEEDED(rc) && pMediumLockList, E_FAIL);
3231 MediumLockList::Base::iterator lockListBegin =
3232 pMediumLockList->GetBegin();
3233 MediumLockList::Base::iterator lockListEnd =
3234 pMediumLockList->GetEnd();
3235 for (MediumLockList::Base::iterator it = lockListBegin;
3236 it != lockListEnd;
3237 )
3238 {
3239 MediumLock &mediumLock = *it;
3240 /* Create a real copy of the medium pointer, as the medium
3241 * lock deletion below would invalidate the referenced object. */
3242 const ComObjPtr<Medium> pMedium = mediumLock.GetMedium();
3243
3244 /* The target and all images not merged (readonly) are skipped */
3245 if ( pMedium == pTarget
3246 || pMedium->getState() == MediumState_LockedRead)
3247 {
3248 ++it;
3249 }
3250 else
3251 {
3252 rc = mParent->unregisterHardDisk(pMedium,
3253 NULL /*pfNeedsSaveSettings*/);
3254 AssertComRC(rc);
3255
3256 /* now, uninitialize the deleted hard disk (note that
3257 * due to the Deleting state, uninit() will not touch
3258 * the parent-child relationship so we need to
3259 * uninitialize each disk individually) */
3260
3261 /* note that the operation initiator hard disk (which is
3262 * normally also the source hard disk) is a special case
3263 * -- there is one more caller added by Task to it which
3264 * we must release. Also, if we are in sync mode, the
3265 * caller may still hold an AutoCaller instance for it
3266 * and therefore we cannot uninit() it (it's therefore
3267 * the caller's responsibility) */
3268 if (pMedium == aSource)
3269 {
3270 Assert(pSource->getChildren().size() == 0);
3271 Assert(pSource->getFirstMachineBackrefId() == NULL);
3272 }
3273
3274 /* Delete the medium lock list entry, which also releases the
3275 * caller added by MergeChain before uninit() and updates the
3276 * iterator to point to the right place. */
3277 rc = pMediumLockList->RemoveByIterator(it);
3278 AssertComRC(rc);
3279
3280 pMedium->uninit();
3281 }
3282
3283 /* Stop as soon as we reached the last medium affected by the merge.
3284 * The remaining images must be kept unchanged. */
3285 if (pMedium == pLast)
3286 break;
3287 }
3288
3289 /* Could be in principle folded into the previous loop, but let's keep
3290 * things simple. Update the medium locking to be the standard state:
3291 * all parent images locked for reading, just the last diff for writing. */
3292 lockListBegin = pMediumLockList->GetBegin();
3293 lockListEnd = pMediumLockList->GetEnd();
3294 MediumLockList::Base::iterator lockListLast = lockListEnd;
3295 lockListLast--;
3296 for (MediumLockList::Base::iterator it = lockListBegin;
3297 it != lockListEnd;
3298 ++it)
3299 {
3300 it->UpdateLock(it == lockListLast);
3301 }
3302
3303
3304 return S_OK;
3305}
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