VirtualBox

source: vbox/trunk/src/VBox/Main/src-server/SnapshotImpl.cpp@ 55214

Last change on this file since 55214 was 55214, checked in by vboxsync, 10 years ago

Main/Console+Machine+Session+Snapshot: move the save state and snapshot related methods from IConsole to IMachine, with lots of unavoidable code restructuring and cleanup. Also define two new machine states (so that the "Saving" one is specifically for saving state now) which requires more changes everywhere
Frontends: necessary adjustments
doc/SDK: document the changes

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 143.5 KB
Line 
1/* $Id: SnapshotImpl.cpp 55214 2015-04-13 15:53:01Z vboxsync $ */
2/** @file
3 * COM class implementation for Snapshot and SnapshotMachine in VBoxSVC.
4 */
5
6/*
7 * Copyright (C) 2006-2015 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.215389.xyz. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18#include "Logging.h"
19#include "SnapshotImpl.h"
20
21#include "MachineImpl.h"
22#include "MediumImpl.h"
23#include "MediumFormatImpl.h"
24#include "Global.h"
25#include "ProgressImpl.h"
26
27// @todo these three includes are required for about one or two lines, try
28// to remove them and put that code in shared code in MachineImplcpp
29#include "SharedFolderImpl.h"
30#include "USBControllerImpl.h"
31#include "USBDeviceFiltersImpl.h"
32#include "VirtualBoxImpl.h"
33
34#include "AutoCaller.h"
35
36#include <iprt/path.h>
37#include <iprt/cpp/utils.h>
38
39#include <VBox/param.h>
40#include <VBox/err.h>
41
42#include <VBox/settings.h>
43
44////////////////////////////////////////////////////////////////////////////////
45//
46// Snapshot private data definition
47//
48////////////////////////////////////////////////////////////////////////////////
49
50typedef std::list< ComObjPtr<Snapshot> > SnapshotsList;
51
52struct Snapshot::Data
53{
54 Data()
55 : pVirtualBox(NULL)
56 {
57 RTTimeSpecSetMilli(&timeStamp, 0);
58 };
59
60 ~Data()
61 {}
62
63 const Guid uuid;
64 Utf8Str strName;
65 Utf8Str strDescription;
66 RTTIMESPEC timeStamp;
67 ComObjPtr<SnapshotMachine> pMachine;
68
69 /** weak VirtualBox parent */
70 VirtualBox * const pVirtualBox;
71
72 // pParent and llChildren are protected by the machine lock
73 ComObjPtr<Snapshot> pParent;
74 SnapshotsList llChildren;
75};
76
77////////////////////////////////////////////////////////////////////////////////
78//
79// Constructor / destructor
80//
81////////////////////////////////////////////////////////////////////////////////
82DEFINE_EMPTY_CTOR_DTOR(Snapshot)
83
84HRESULT Snapshot::FinalConstruct()
85{
86 LogFlowThisFunc(("\n"));
87 return BaseFinalConstruct();
88}
89
90void Snapshot::FinalRelease()
91{
92 LogFlowThisFunc(("\n"));
93 uninit();
94 BaseFinalRelease();
95}
96
97/**
98 * Initializes the instance
99 *
100 * @param aId id of the snapshot
101 * @param aName name of the snapshot
102 * @param aDescription name of the snapshot (NULL if no description)
103 * @param aTimeStamp timestamp of the snapshot, in ms since 1970-01-01 UTC
104 * @param aMachine machine associated with this snapshot
105 * @param aParent parent snapshot (NULL if no parent)
106 */
107HRESULT Snapshot::init(VirtualBox *aVirtualBox,
108 const Guid &aId,
109 const Utf8Str &aName,
110 const Utf8Str &aDescription,
111 const RTTIMESPEC &aTimeStamp,
112 SnapshotMachine *aMachine,
113 Snapshot *aParent)
114{
115 LogFlowThisFunc(("uuid=%s aParent->uuid=%s\n", aId.toString().c_str(), (aParent) ? aParent->m->uuid.toString().c_str() : ""));
116
117 ComAssertRet(!aId.isZero() && aId.isValid() && !aName.isEmpty() && aMachine, E_INVALIDARG);
118
119 /* Enclose the state transition NotReady->InInit->Ready */
120 AutoInitSpan autoInitSpan(this);
121 AssertReturn(autoInitSpan.isOk(), E_FAIL);
122
123 m = new Data;
124
125 /* share parent weakly */
126 unconst(m->pVirtualBox) = aVirtualBox;
127
128 m->pParent = aParent;
129
130 unconst(m->uuid) = aId;
131 m->strName = aName;
132 m->strDescription = aDescription;
133 m->timeStamp = aTimeStamp;
134 m->pMachine = aMachine;
135
136 if (aParent)
137 aParent->m->llChildren.push_back(this);
138
139 /* Confirm a successful initialization when it's the case */
140 autoInitSpan.setSucceeded();
141
142 return S_OK;
143}
144
145/**
146 * Uninitializes the instance and sets the ready flag to FALSE.
147 * Called either from FinalRelease(), by the parent when it gets destroyed,
148 * or by a third party when it decides this object is no more valid.
149 *
150 * Since this manipulates the snapshots tree, the caller must hold the
151 * machine lock in write mode (which protects the snapshots tree)!
152 */
153void Snapshot::uninit()
154{
155 LogFlowThisFunc(("\n"));
156
157 /* Enclose the state transition Ready->InUninit->NotReady */
158 AutoUninitSpan autoUninitSpan(this);
159 if (autoUninitSpan.uninitDone())
160 return;
161
162 Assert(m->pMachine->isWriteLockOnCurrentThread());
163
164 // uninit all children
165 SnapshotsList::iterator it;
166 for (it = m->llChildren.begin();
167 it != m->llChildren.end();
168 ++it)
169 {
170 Snapshot *pChild = *it;
171 pChild->m->pParent.setNull();
172 pChild->uninit();
173 }
174 m->llChildren.clear(); // this unsets all the ComPtrs and probably calls delete
175
176 // since there is no guarantee anyone holds a reference to us except the
177 // list of children in our parent, make sure that the reference count
178 // will not drop to 0 before we've declared ourselves as uninitialized,
179 // otherwise there will be another uninit call which causes a self-deadlock
180 // because this uninit isn't complete yet.
181 ComObjPtr<Snapshot> pSnapshot(this);
182 if (m->pParent)
183 i_deparent();
184
185 if (m->pMachine)
186 {
187 m->pMachine->uninit();
188 m->pMachine.setNull();
189 }
190
191 delete m;
192 m = NULL;
193
194 autoUninitSpan.setSucceeded();
195 // see above, now the refcount may reach 0
196 pSnapshot.setNull();
197}
198
199/**
200 * Delete the current snapshot by removing it from the tree of snapshots
201 * and reparenting its children.
202 *
203 * After this, the caller must call uninit() on the snapshot. We can't call
204 * that from here because if we do, the AutoUninitSpan waits forever for
205 * the number of callers to become 0 (it is 1 because of the AutoCaller in here).
206 *
207 * NOTE: this does NOT lock the snapshot, it is assumed that the machine state
208 * (and the snapshots tree) is protected by the caller having requested the machine
209 * lock in write mode AND the machine state must be DeletingSnapshot.
210 */
211void Snapshot::i_beginSnapshotDelete()
212{
213 AutoCaller autoCaller(this);
214 if (FAILED(autoCaller.rc()))
215 return;
216
217 // caller must have acquired the machine's write lock
218 Assert( m->pMachine->mData->mMachineState == MachineState_DeletingSnapshot
219 || m->pMachine->mData->mMachineState == MachineState_DeletingSnapshotOnline
220 || m->pMachine->mData->mMachineState == MachineState_DeletingSnapshotPaused);
221 Assert(m->pMachine->isWriteLockOnCurrentThread());
222
223 // the snapshot must have only one child when being deleted or no children at all
224 AssertReturnVoid(m->llChildren.size() <= 1);
225
226 ComObjPtr<Snapshot> parentSnapshot = m->pParent;
227
228 /// @todo (dmik):
229 // when we introduce clones later, deleting the snapshot will affect
230 // the current and first snapshots of clones, if they are direct children
231 // of this snapshot. So we will need to lock machines associated with
232 // child snapshots as well and update mCurrentSnapshot and/or
233 // mFirstSnapshot fields.
234
235 if (this == m->pMachine->mData->mCurrentSnapshot)
236 {
237 m->pMachine->mData->mCurrentSnapshot = parentSnapshot;
238
239 /* we've changed the base of the current state so mark it as
240 * modified as it no longer guaranteed to be its copy */
241 m->pMachine->mData->mCurrentStateModified = TRUE;
242 }
243
244 if (this == m->pMachine->mData->mFirstSnapshot)
245 {
246 if (m->llChildren.size() == 1)
247 {
248 ComObjPtr<Snapshot> childSnapshot = m->llChildren.front();
249 m->pMachine->mData->mFirstSnapshot = childSnapshot;
250 }
251 else
252 m->pMachine->mData->mFirstSnapshot.setNull();
253 }
254
255 // reparent our children
256 for (SnapshotsList::const_iterator it = m->llChildren.begin();
257 it != m->llChildren.end();
258 ++it)
259 {
260 ComObjPtr<Snapshot> child = *it;
261 // no need to lock, snapshots tree is protected by machine lock
262 child->m->pParent = m->pParent;
263 if (m->pParent)
264 m->pParent->m->llChildren.push_back(child);
265 }
266
267 // clear our own children list (since we reparented the children)
268 m->llChildren.clear();
269}
270
271/**
272 * Internal helper that removes "this" from the list of children of its
273 * parent. Used in uninit() and other places when reparenting is necessary.
274 *
275 * The caller must hold the machine lock in write mode (which protects the snapshots tree)!
276 */
277void Snapshot::i_deparent()
278{
279 Assert(m->pMachine->isWriteLockOnCurrentThread());
280
281 SnapshotsList &llParent = m->pParent->m->llChildren;
282 for (SnapshotsList::iterator it = llParent.begin();
283 it != llParent.end();
284 ++it)
285 {
286 Snapshot *pParentsChild = *it;
287 if (this == pParentsChild)
288 {
289 llParent.erase(it);
290 break;
291 }
292 }
293
294 m->pParent.setNull();
295}
296
297////////////////////////////////////////////////////////////////////////////////
298//
299// ISnapshot public methods
300//
301////////////////////////////////////////////////////////////////////////////////
302
303HRESULT Snapshot::getId(com::Guid &aId)
304{
305 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
306
307 aId = m->uuid;
308
309 return S_OK;
310}
311
312HRESULT Snapshot::getName(com::Utf8Str &aName)
313{
314 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
315 aName = m->strName;
316 return S_OK;
317}
318
319/**
320 * @note Locks this object for writing, then calls Machine::onSnapshotChange()
321 * (see its lock requirements).
322 */
323HRESULT Snapshot::setName(const com::Utf8Str &aName)
324{
325 HRESULT rc = S_OK;
326
327 // prohibit setting a UUID only as the machine name, or else it can
328 // never be found by findMachine()
329 Guid test(aName);
330
331 if (!test.isZero() && test.isValid())
332 return setError(E_INVALIDARG, tr("A machine cannot have a UUID as its name"));
333
334 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
335
336 if (m->strName != aName)
337 {
338 m->strName = aName;
339 alock.release(); /* Important! (child->parent locks are forbidden) */
340 rc = m->pMachine->i_onSnapshotChange(this);
341 }
342
343 return rc;
344}
345
346HRESULT Snapshot::getDescription(com::Utf8Str &aDescription)
347{
348 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
349 aDescription = m->strDescription;
350 return S_OK;
351}
352
353HRESULT Snapshot::setDescription(const com::Utf8Str &aDescription)
354{
355 HRESULT rc = S_OK;
356
357 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
358 if (m->strDescription != aDescription)
359 {
360 m->strDescription = aDescription;
361 alock.release(); /* Important! (child->parent locks are forbidden) */
362 rc = m->pMachine->i_onSnapshotChange(this);
363 }
364
365 return rc;
366}
367
368HRESULT Snapshot::getTimeStamp(LONG64 *aTimeStamp)
369{
370 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
371
372 *aTimeStamp = RTTimeSpecGetMilli(&m->timeStamp);
373 return S_OK;
374}
375
376HRESULT Snapshot::getOnline(BOOL *aOnline)
377{
378 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
379
380 *aOnline = i_getStateFilePath().isNotEmpty();
381 return S_OK;
382}
383
384HRESULT Snapshot::getMachine(ComPtr<IMachine> &aMachine)
385{
386 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
387
388 m->pMachine.queryInterfaceTo(aMachine.asOutParam());
389
390 return S_OK;
391}
392
393
394HRESULT Snapshot::getParent(ComPtr<ISnapshot> &aParent)
395{
396 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
397
398 m->pParent.queryInterfaceTo(aParent.asOutParam());
399 return S_OK;
400}
401
402HRESULT Snapshot::getChildren(std::vector<ComPtr<ISnapshot> > &aChildren)
403{
404 // snapshots tree is protected by machine lock
405 AutoReadLock alock(m->pMachine COMMA_LOCKVAL_SRC_POS);
406 aChildren.resize(0);
407 for (SnapshotsList::const_iterator it = m->llChildren.begin();
408 it != m->llChildren.end();
409 ++it)
410 aChildren.push_back(*it);
411 return S_OK;
412}
413
414HRESULT Snapshot::getChildrenCount(ULONG *count)
415{
416 *count = i_getChildrenCount();
417
418 return S_OK;
419}
420
421////////////////////////////////////////////////////////////////////////////////
422//
423// Snapshot public internal methods
424//
425////////////////////////////////////////////////////////////////////////////////
426
427/**
428 * Returns the parent snapshot or NULL if there's none. Must have caller + locking!
429 * @return
430 */
431const ComObjPtr<Snapshot>& Snapshot::i_getParent() const
432{
433 return m->pParent;
434}
435
436/**
437 * Returns the first child snapshot or NULL if there's none. Must have caller + locking!
438 * @return
439 */
440const ComObjPtr<Snapshot> Snapshot::i_getFirstChild() const
441{
442 if (!m->llChildren.size())
443 return NULL;
444 return m->llChildren.front();
445}
446
447/**
448 * @note
449 * Must be called from under the object's lock!
450 */
451const Utf8Str& Snapshot::i_getStateFilePath() const
452{
453 return m->pMachine->mSSData->strStateFilePath;
454}
455
456/**
457 * Returns the depth in the snapshot tree for this snapshot.
458 *
459 * @note takes the snapshot tree lock
460 */
461
462uint32_t Snapshot::i_getDepth()
463{
464 AutoCaller autoCaller(this);
465 AssertComRC(autoCaller.rc());
466
467 // snapshots tree is protected by machine lock
468 AutoReadLock alock(m->pMachine COMMA_LOCKVAL_SRC_POS);
469
470 uint32_t cDepth = 0;
471 ComObjPtr<Snapshot> pSnap(this);
472 while (!pSnap.isNull())
473 {
474 pSnap = pSnap->m->pParent;
475 cDepth++;
476 }
477
478 return cDepth;
479}
480
481/**
482 * Returns the number of direct child snapshots, without grandchildren.
483 * Does not recurse.
484 * @return
485 */
486ULONG Snapshot::i_getChildrenCount()
487{
488 AutoCaller autoCaller(this);
489 AssertComRC(autoCaller.rc());
490
491 // snapshots tree is protected by machine lock
492 AutoReadLock alock(m->pMachine COMMA_LOCKVAL_SRC_POS);
493
494 return (ULONG)m->llChildren.size();
495}
496
497/**
498 * Implementation method for getAllChildrenCount() so we request the
499 * tree lock only once before recursing. Don't call directly.
500 * @return
501 */
502ULONG Snapshot::i_getAllChildrenCountImpl()
503{
504 AutoCaller autoCaller(this);
505 AssertComRC(autoCaller.rc());
506
507 ULONG count = (ULONG)m->llChildren.size();
508 for (SnapshotsList::const_iterator it = m->llChildren.begin();
509 it != m->llChildren.end();
510 ++it)
511 {
512 count += (*it)->i_getAllChildrenCountImpl();
513 }
514
515 return count;
516}
517
518/**
519 * Returns the number of child snapshots including all grandchildren.
520 * Recurses into the snapshots tree.
521 * @return
522 */
523ULONG Snapshot::i_getAllChildrenCount()
524{
525 AutoCaller autoCaller(this);
526 AssertComRC(autoCaller.rc());
527
528 // snapshots tree is protected by machine lock
529 AutoReadLock alock(m->pMachine COMMA_LOCKVAL_SRC_POS);
530
531 return i_getAllChildrenCountImpl();
532}
533
534/**
535 * Returns the SnapshotMachine that this snapshot belongs to.
536 * Caller must hold the snapshot's object lock!
537 * @return
538 */
539const ComObjPtr<SnapshotMachine>& Snapshot::i_getSnapshotMachine() const
540{
541 return m->pMachine;
542}
543
544/**
545 * Returns the UUID of this snapshot.
546 * Caller must hold the snapshot's object lock!
547 * @return
548 */
549Guid Snapshot::i_getId() const
550{
551 return m->uuid;
552}
553
554/**
555 * Returns the name of this snapshot.
556 * Caller must hold the snapshot's object lock!
557 * @return
558 */
559const Utf8Str& Snapshot::i_getName() const
560{
561 return m->strName;
562}
563
564/**
565 * Returns the time stamp of this snapshot.
566 * Caller must hold the snapshot's object lock!
567 * @return
568 */
569RTTIMESPEC Snapshot::i_getTimeStamp() const
570{
571 return m->timeStamp;
572}
573
574/**
575 * Searches for a snapshot with the given ID among children, grand-children,
576 * etc. of this snapshot. This snapshot itself is also included in the search.
577 *
578 * Caller must hold the machine lock (which protects the snapshots tree!)
579 */
580ComObjPtr<Snapshot> Snapshot::i_findChildOrSelf(IN_GUID aId)
581{
582 ComObjPtr<Snapshot> child;
583
584 AutoCaller autoCaller(this);
585 AssertComRC(autoCaller.rc());
586
587 // no need to lock, uuid is const
588 if (m->uuid == aId)
589 child = this;
590 else
591 {
592 for (SnapshotsList::const_iterator it = m->llChildren.begin();
593 it != m->llChildren.end();
594 ++it)
595 {
596 if ((child = (*it)->i_findChildOrSelf(aId)))
597 break;
598 }
599 }
600
601 return child;
602}
603
604/**
605 * Searches for a first snapshot with the given name among children,
606 * grand-children, etc. of this snapshot. This snapshot itself is also included
607 * in the search.
608 *
609 * Caller must hold the machine lock (which protects the snapshots tree!)
610 */
611ComObjPtr<Snapshot> Snapshot::i_findChildOrSelf(const Utf8Str &aName)
612{
613 ComObjPtr<Snapshot> child;
614 AssertReturn(!aName.isEmpty(), child);
615
616 AutoCaller autoCaller(this);
617 AssertComRC(autoCaller.rc());
618
619 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
620
621 if (m->strName == aName)
622 child = this;
623 else
624 {
625 alock.release();
626 for (SnapshotsList::const_iterator it = m->llChildren.begin();
627 it != m->llChildren.end();
628 ++it)
629 {
630 if ((child = (*it)->i_findChildOrSelf(aName)))
631 break;
632 }
633 }
634
635 return child;
636}
637
638/**
639 * Internal implementation for Snapshot::updateSavedStatePaths (below).
640 * @param aOldPath
641 * @param aNewPath
642 */
643void Snapshot::i_updateSavedStatePathsImpl(const Utf8Str &strOldPath,
644 const Utf8Str &strNewPath)
645{
646 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
647
648 const Utf8Str &path = m->pMachine->mSSData->strStateFilePath;
649 LogFlowThisFunc(("Snap[%s].statePath={%s}\n", m->strName.c_str(), path.c_str()));
650
651 /* state file may be NULL (for offline snapshots) */
652 if ( path.length()
653 && RTPathStartsWith(path.c_str(), strOldPath.c_str())
654 )
655 {
656 m->pMachine->mSSData->strStateFilePath = Utf8StrFmt("%s%s",
657 strNewPath.c_str(),
658 path.c_str() + strOldPath.length());
659 LogFlowThisFunc(("-> updated: {%s}\n", path.c_str()));
660 }
661
662 for (SnapshotsList::const_iterator it = m->llChildren.begin();
663 it != m->llChildren.end();
664 ++it)
665 {
666 Snapshot *pChild = *it;
667 pChild->i_updateSavedStatePathsImpl(strOldPath, strNewPath);
668 }
669}
670
671/**
672 * Returns true if this snapshot or one of its children uses the given file,
673 * whose path must be fully qualified, as its saved state. When invoked on a
674 * machine's first snapshot, this can be used to check if a saved state file
675 * is shared with any snapshots.
676 *
677 * Caller must hold the machine lock, which protects the snapshots tree.
678 *
679 * @param strPath
680 * @param pSnapshotToIgnore If != NULL, this snapshot is ignored during the checks.
681 * @return
682 */
683bool Snapshot::i_sharesSavedStateFile(const Utf8Str &strPath,
684 Snapshot *pSnapshotToIgnore)
685{
686 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
687 const Utf8Str &path = m->pMachine->mSSData->strStateFilePath;
688
689 if (!pSnapshotToIgnore || pSnapshotToIgnore != this)
690 if (path.isNotEmpty())
691 if (path == strPath)
692 return true; // no need to recurse then
693
694 // but otherwise we must check children
695 for (SnapshotsList::const_iterator it = m->llChildren.begin();
696 it != m->llChildren.end();
697 ++it)
698 {
699 Snapshot *pChild = *it;
700 if (!pSnapshotToIgnore || pSnapshotToIgnore != pChild)
701 if (pChild->i_sharesSavedStateFile(strPath, pSnapshotToIgnore))
702 return true;
703 }
704
705 return false;
706}
707
708
709/**
710 * Checks if the specified path change affects the saved state file path of
711 * this snapshot or any of its (grand-)children and updates it accordingly.
712 *
713 * Intended to be called by Machine::openConfigLoader() only.
714 *
715 * @param aOldPath old path (full)
716 * @param aNewPath new path (full)
717 *
718 * @note Locks the machine (for the snapshots tree) + this object + children for writing.
719 */
720void Snapshot::i_updateSavedStatePaths(const Utf8Str &strOldPath,
721 const Utf8Str &strNewPath)
722{
723 LogFlowThisFunc(("aOldPath={%s} aNewPath={%s}\n", strOldPath.c_str(), strNewPath.c_str()));
724
725 AutoCaller autoCaller(this);
726 AssertComRC(autoCaller.rc());
727
728 // snapshots tree is protected by machine lock
729 AutoWriteLock alock(m->pMachine COMMA_LOCKVAL_SRC_POS);
730
731 // call the implementation under the tree lock
732 i_updateSavedStatePathsImpl(strOldPath, strNewPath);
733}
734
735/**
736 * Saves the settings attributes of one snapshot.
737 *
738 * @param data Target for saving snapshot settings.
739 * @return
740 */
741HRESULT Snapshot::i_saveSnapshotImplOne(settings::Snapshot &data) const
742{
743 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
744
745 data.uuid = m->uuid;
746 data.strName = m->strName;
747 data.timestamp = m->timeStamp;
748 data.strDescription = m->strDescription;
749
750 // state file (only if this snapshot is online)
751 if (i_getStateFilePath().isNotEmpty())
752 m->pMachine->i_copyPathRelativeToMachine(i_getStateFilePath(), data.strStateFile);
753 else
754 data.strStateFile.setNull();
755
756 HRESULT rc = m->pMachine->i_saveHardware(data.hardware, &data.debugging, &data.autostart);
757 if (FAILED(rc)) return rc;
758
759 rc = m->pMachine->i_saveStorageControllers(data.storage);
760 if (FAILED(rc)) return rc;
761
762 return S_OK;
763}
764
765/**
766 * Internal implementation for Snapshot::saveSnapshot (below). Caller has
767 * requested the snapshots tree (machine) lock.
768 *
769 * @param data Target for saving snapshot settings.
770 * @return
771 */
772HRESULT Snapshot::i_saveSnapshotImpl(settings::Snapshot &data) const
773{
774 HRESULT rc = i_saveSnapshotImplOne(data);
775 if (FAILED(rc))
776 return rc;
777
778 settings::SnapshotsList &llSettingsChildren = data.llChildSnapshots;
779 for (SnapshotsList::const_iterator it = m->llChildren.begin();
780 it != m->llChildren.end();
781 ++it)
782 {
783 // Use the heap (indirectly through the list container) to reduce the
784 // stack footprint, avoiding local settings objects on the stack which
785 // need a lot of stack space. There can be VMs with deeply nested
786 // snapshots. The stack can be quite small, especially with XPCOM.
787 llSettingsChildren.push_back(settings::g_SnapshotEmpty);
788 Snapshot *pSnap = *it;
789 rc = pSnap->i_saveSnapshotImpl(llSettingsChildren.back());
790 if (FAILED(rc))
791 {
792 llSettingsChildren.pop_back();
793 return rc;
794 }
795 }
796
797 return S_OK;
798}
799
800/**
801 * Saves the given snapshot and all its children.
802 * It is assumed that the given node is empty.
803 *
804 * @param data Target for saving snapshot settings.
805 */
806HRESULT Snapshot::i_saveSnapshot(settings::Snapshot &data) const
807{
808 // snapshots tree is protected by machine lock
809 AutoReadLock alock(m->pMachine COMMA_LOCKVAL_SRC_POS);
810
811 return i_saveSnapshotImpl(data);
812}
813
814/**
815 * Part of the cleanup engine of Machine::Unregister().
816 *
817 * This removes all medium attachments from the snapshot's machine and returns
818 * the snapshot's saved state file name, if any, and then calls uninit() on
819 * "this" itself.
820 *
821 * Caller must hold the machine write lock (which protects the snapshots tree!)
822 *
823 * @param writeLock Machine write lock, which can get released temporarily here.
824 * @param cleanupMode Cleanup mode; see Machine::detachAllMedia().
825 * @param llMedia List of media returned to caller, depending on cleanupMode.
826 * @param llFilenames
827 * @return
828 */
829HRESULT Snapshot::i_uninitOne(AutoWriteLock &writeLock,
830 CleanupMode_T cleanupMode,
831 MediaList &llMedia,
832 std::list<Utf8Str> &llFilenames)
833{
834 // now call detachAllMedia on the snapshot machine
835 HRESULT rc = m->pMachine->i_detachAllMedia(writeLock,
836 this /* pSnapshot */,
837 cleanupMode,
838 llMedia);
839 if (FAILED(rc))
840 return rc;
841
842 // report the saved state file if it's not on the list yet
843 if (!m->pMachine->mSSData->strStateFilePath.isEmpty())
844 {
845 bool fFound = false;
846 for (std::list<Utf8Str>::const_iterator it = llFilenames.begin();
847 it != llFilenames.end();
848 ++it)
849 {
850 const Utf8Str &str = *it;
851 if (str == m->pMachine->mSSData->strStateFilePath)
852 {
853 fFound = true;
854 break;
855 }
856 }
857 if (!fFound)
858 llFilenames.push_back(m->pMachine->mSSData->strStateFilePath);
859 }
860
861 i_beginSnapshotDelete();
862 uninit();
863
864 return S_OK;
865}
866
867/**
868 * Part of the cleanup engine of Machine::Unregister().
869 *
870 * This recursively removes all medium attachments from the snapshot's machine
871 * and returns the snapshot's saved state file name, if any, and then calls
872 * uninit() on "this" itself.
873 *
874 * This recurses into children first, so the given MediaList receives child
875 * media first before their parents. If the caller wants to close all media,
876 * they should go thru the list from the beginning to the end because media
877 * cannot be closed if they have children.
878 *
879 * This calls uninit() on itself, so the snapshots tree (beginning with a machine's pFirstSnapshot) becomes invalid after this.
880 * It does not alter the main machine's snapshot pointers (pFirstSnapshot, pCurrentSnapshot).
881 *
882 * Caller must hold the machine write lock (which protects the snapshots tree!)
883 *
884 * @param writeLock Machine write lock, which can get released temporarily here.
885 * @param cleanupMode Cleanup mode; see Machine::detachAllMedia().
886 * @param llMedia List of media returned to caller, depending on cleanupMode.
887 * @param llFilenames
888 * @return
889 */
890HRESULT Snapshot::i_uninitRecursively(AutoWriteLock &writeLock,
891 CleanupMode_T cleanupMode,
892 MediaList &llMedia,
893 std::list<Utf8Str> &llFilenames)
894{
895 Assert(m->pMachine->isWriteLockOnCurrentThread());
896
897 HRESULT rc = S_OK;
898
899 // make a copy of the Guid for logging before we uninit ourselves
900#ifdef LOG_ENABLED
901 Guid uuid = i_getId();
902 Utf8Str name = i_getName();
903 LogFlowThisFunc(("Entering for snapshot '%s' {%RTuuid}\n", name.c_str(), uuid.raw()));
904#endif
905
906 // Recurse into children first so that the child media appear on the list
907 // first; this way caller can close the media from the beginning to the end
908 // because parent media can't be closed if they have children and
909 // additionally it postpones the uninit() call until we no longer need
910 // anything from the list. Oh, and remember that the child removes itself
911 // from the list, so keep the iterator at the beginning.
912 for (SnapshotsList::const_iterator it = m->llChildren.begin();
913 it != m->llChildren.end();
914 it = m->llChildren.begin())
915 {
916 Snapshot *pChild = *it;
917 rc = pChild->i_uninitRecursively(writeLock, cleanupMode, llMedia, llFilenames);
918 if (FAILED(rc))
919 break;
920 }
921
922 if (SUCCEEDED(rc))
923 rc = i_uninitOne(writeLock, cleanupMode, llMedia, llFilenames);
924
925#ifdef LOG_ENABLED
926 LogFlowThisFunc(("Leaving for snapshot '%s' {%RTuuid}: %Rhrc\n", name.c_str(), uuid.raw(), rc));
927#endif
928
929 return rc;
930}
931
932////////////////////////////////////////////////////////////////////////////////
933//
934// SnapshotMachine implementation
935//
936////////////////////////////////////////////////////////////////////////////////
937
938SnapshotMachine::SnapshotMachine()
939 : mMachine(NULL)
940{}
941
942SnapshotMachine::~SnapshotMachine()
943{}
944
945HRESULT SnapshotMachine::FinalConstruct()
946{
947 LogFlowThisFunc(("\n"));
948
949 return BaseFinalConstruct();
950}
951
952void SnapshotMachine::FinalRelease()
953{
954 LogFlowThisFunc(("\n"));
955
956 uninit();
957
958 BaseFinalRelease();
959}
960
961/**
962 * Initializes the SnapshotMachine object when taking a snapshot.
963 *
964 * @param aSessionMachine machine to take a snapshot from
965 * @param aSnapshotId snapshot ID of this snapshot machine
966 * @param aStateFilePath file where the execution state will be later saved
967 * (or NULL for the offline snapshot)
968 *
969 * @note The aSessionMachine must be locked for writing.
970 */
971HRESULT SnapshotMachine::init(SessionMachine *aSessionMachine,
972 IN_GUID aSnapshotId,
973 const Utf8Str &aStateFilePath)
974{
975 LogFlowThisFuncEnter();
976 LogFlowThisFunc(("mName={%s}\n", aSessionMachine->mUserData->s.strName.c_str()));
977
978 Guid l_guid(aSnapshotId);
979 AssertReturn(aSessionMachine && (!l_guid.isZero() && l_guid.isValid()), E_INVALIDARG);
980
981 /* Enclose the state transition NotReady->InInit->Ready */
982 AutoInitSpan autoInitSpan(this);
983 AssertReturn(autoInitSpan.isOk(), E_FAIL);
984
985 AssertReturn(aSessionMachine->isWriteLockOnCurrentThread(), E_FAIL);
986
987 mSnapshotId = aSnapshotId;
988 ComObjPtr<Machine> pMachine = aSessionMachine->mPeer;
989
990 /* mPeer stays NULL */
991 /* memorize the primary Machine instance (i.e. not SessionMachine!) */
992 unconst(mMachine) = pMachine;
993 /* share the parent pointer */
994 unconst(mParent) = pMachine->mParent;
995
996 /* take the pointer to Data to share */
997 mData.share(pMachine->mData);
998
999 /* take the pointer to UserData to share (our UserData must always be the
1000 * same as Machine's data) */
1001 mUserData.share(pMachine->mUserData);
1002 /* make a private copy of all other data (recent changes from SessionMachine) */
1003 mHWData.attachCopy(aSessionMachine->mHWData);
1004 mMediaData.attachCopy(aSessionMachine->mMediaData);
1005
1006 /* SSData is always unique for SnapshotMachine */
1007 mSSData.allocate();
1008 mSSData->strStateFilePath = aStateFilePath;
1009
1010 HRESULT rc = S_OK;
1011
1012 /* create copies of all shared folders (mHWData after attaching a copy
1013 * contains just references to original objects) */
1014 for (HWData::SharedFolderList::iterator it = mHWData->mSharedFolders.begin();
1015 it != mHWData->mSharedFolders.end();
1016 ++it)
1017 {
1018 ComObjPtr<SharedFolder> folder;
1019 folder.createObject();
1020 rc = folder->initCopy(this, *it);
1021 if (FAILED(rc)) return rc;
1022 *it = folder;
1023 }
1024
1025 /* associate hard disks with the snapshot
1026 * (Machine::uninitDataAndChildObjects() will deassociate at destruction) */
1027 for (MediaData::AttachmentList::const_iterator it = mMediaData->mAttachments.begin();
1028 it != mMediaData->mAttachments.end();
1029 ++it)
1030 {
1031 MediumAttachment *pAtt = *it;
1032 Medium *pMedium = pAtt->i_getMedium();
1033 if (pMedium) // can be NULL for non-harddisk
1034 {
1035 rc = pMedium->i_addBackReference(mData->mUuid, mSnapshotId);
1036 AssertComRC(rc);
1037 }
1038 }
1039
1040 /* create copies of all storage controllers (mStorageControllerData
1041 * after attaching a copy contains just references to original objects) */
1042 mStorageControllers.allocate();
1043 for (StorageControllerList::const_iterator
1044 it = aSessionMachine->mStorageControllers->begin();
1045 it != aSessionMachine->mStorageControllers->end();
1046 ++it)
1047 {
1048 ComObjPtr<StorageController> ctrl;
1049 ctrl.createObject();
1050 ctrl->initCopy(this, *it);
1051 mStorageControllers->push_back(ctrl);
1052 }
1053
1054 /* create all other child objects that will be immutable private copies */
1055
1056 unconst(mBIOSSettings).createObject();
1057 mBIOSSettings->initCopy(this, pMachine->mBIOSSettings);
1058
1059 unconst(mVRDEServer).createObject();
1060 mVRDEServer->initCopy(this, pMachine->mVRDEServer);
1061
1062 unconst(mAudioAdapter).createObject();
1063 mAudioAdapter->initCopy(this, pMachine->mAudioAdapter);
1064
1065 /* create copies of all USB controllers (mUSBControllerData
1066 * after attaching a copy contains just references to original objects) */
1067 mUSBControllers.allocate();
1068 for (USBControllerList::const_iterator
1069 it = aSessionMachine->mUSBControllers->begin();
1070 it != aSessionMachine->mUSBControllers->end();
1071 ++it)
1072 {
1073 ComObjPtr<USBController> ctrl;
1074 ctrl.createObject();
1075 ctrl->initCopy(this, *it);
1076 mUSBControllers->push_back(ctrl);
1077 }
1078
1079 unconst(mUSBDeviceFilters).createObject();
1080 mUSBDeviceFilters->initCopy(this, pMachine->mUSBDeviceFilters);
1081
1082 mNetworkAdapters.resize(pMachine->mNetworkAdapters.size());
1083 for (ULONG slot = 0; slot < mNetworkAdapters.size(); slot++)
1084 {
1085 unconst(mNetworkAdapters[slot]).createObject();
1086 mNetworkAdapters[slot]->initCopy(this, pMachine->mNetworkAdapters[slot]);
1087 }
1088
1089 for (ULONG slot = 0; slot < RT_ELEMENTS(mSerialPorts); slot++)
1090 {
1091 unconst(mSerialPorts[slot]).createObject();
1092 mSerialPorts[slot]->initCopy(this, pMachine->mSerialPorts[slot]);
1093 }
1094
1095 for (ULONG slot = 0; slot < RT_ELEMENTS(mParallelPorts); slot++)
1096 {
1097 unconst(mParallelPorts[slot]).createObject();
1098 mParallelPorts[slot]->initCopy(this, pMachine->mParallelPorts[slot]);
1099 }
1100
1101 unconst(mBandwidthControl).createObject();
1102 mBandwidthControl->initCopy(this, pMachine->mBandwidthControl);
1103
1104 /* Confirm a successful initialization when it's the case */
1105 autoInitSpan.setSucceeded();
1106
1107 LogFlowThisFuncLeave();
1108 return S_OK;
1109}
1110
1111/**
1112 * Initializes the SnapshotMachine object when loading from the settings file.
1113 *
1114 * @param aMachine machine the snapshot belongs to
1115 * @param aHWNode <Hardware> node
1116 * @param aHDAsNode <HardDiskAttachments> node
1117 * @param aSnapshotId snapshot ID of this snapshot machine
1118 * @param aStateFilePath file where the execution state is saved
1119 * (or NULL for the offline snapshot)
1120 *
1121 * @note Doesn't lock anything.
1122 */
1123HRESULT SnapshotMachine::initFromSettings(Machine *aMachine,
1124 const settings::Hardware &hardware,
1125 const settings::Debugging *pDbg,
1126 const settings::Autostart *pAutostart,
1127 const settings::Storage &storage,
1128 IN_GUID aSnapshotId,
1129 const Utf8Str &aStateFilePath)
1130{
1131 LogFlowThisFuncEnter();
1132 LogFlowThisFunc(("mName={%s}\n", aMachine->mUserData->s.strName.c_str()));
1133
1134 Guid l_guid(aSnapshotId);
1135 AssertReturn(aMachine && (!l_guid.isZero() && l_guid.isValid()), E_INVALIDARG);
1136
1137 /* Enclose the state transition NotReady->InInit->Ready */
1138 AutoInitSpan autoInitSpan(this);
1139 AssertReturn(autoInitSpan.isOk(), E_FAIL);
1140
1141 /* Don't need to lock aMachine when VirtualBox is starting up */
1142
1143 mSnapshotId = aSnapshotId;
1144
1145 /* mPeer stays NULL */
1146 /* memorize the primary Machine instance (i.e. not SessionMachine!) */
1147 unconst(mMachine) = aMachine;
1148 /* share the parent pointer */
1149 unconst(mParent) = aMachine->mParent;
1150
1151 /* take the pointer to Data to share */
1152 mData.share(aMachine->mData);
1153 /*
1154 * take the pointer to UserData to share
1155 * (our UserData must always be the same as Machine's data)
1156 */
1157 mUserData.share(aMachine->mUserData);
1158 /* allocate private copies of all other data (will be loaded from settings) */
1159 mHWData.allocate();
1160 mMediaData.allocate();
1161 mStorageControllers.allocate();
1162 mUSBControllers.allocate();
1163
1164 /* SSData is always unique for SnapshotMachine */
1165 mSSData.allocate();
1166 mSSData->strStateFilePath = aStateFilePath;
1167
1168 /* create all other child objects that will be immutable private copies */
1169
1170 unconst(mBIOSSettings).createObject();
1171 mBIOSSettings->init(this);
1172
1173 unconst(mVRDEServer).createObject();
1174 mVRDEServer->init(this);
1175
1176 unconst(mAudioAdapter).createObject();
1177 mAudioAdapter->init(this);
1178
1179 unconst(mUSBDeviceFilters).createObject();
1180 mUSBDeviceFilters->init(this);
1181
1182 mNetworkAdapters.resize(Global::getMaxNetworkAdapters(mHWData->mChipsetType));
1183 for (ULONG slot = 0; slot < mNetworkAdapters.size(); slot++)
1184 {
1185 unconst(mNetworkAdapters[slot]).createObject();
1186 mNetworkAdapters[slot]->init(this, slot);
1187 }
1188
1189 for (ULONG slot = 0; slot < RT_ELEMENTS(mSerialPorts); slot++)
1190 {
1191 unconst(mSerialPorts[slot]).createObject();
1192 mSerialPorts[slot]->init(this, slot);
1193 }
1194
1195 for (ULONG slot = 0; slot < RT_ELEMENTS(mParallelPorts); slot++)
1196 {
1197 unconst(mParallelPorts[slot]).createObject();
1198 mParallelPorts[slot]->init(this, slot);
1199 }
1200
1201 unconst(mBandwidthControl).createObject();
1202 mBandwidthControl->init(this);
1203
1204 /* load hardware and harddisk settings */
1205
1206 HRESULT rc = i_loadHardware(hardware, pDbg, pAutostart);
1207 if (SUCCEEDED(rc))
1208 rc = i_loadStorageControllers(storage,
1209 NULL, /* puuidRegistry */
1210 &mSnapshotId);
1211
1212 if (SUCCEEDED(rc))
1213 /* commit all changes made during the initialization */
1214 i_commit(); /// @todo r=dj why do we need a commit in init?!? this is very expensive
1215 /// @todo r=klaus for some reason the settings loading logic backs up
1216 // the settings, and therefore a commit is needed. Should probably be changed.
1217
1218 /* Confirm a successful initialization when it's the case */
1219 if (SUCCEEDED(rc))
1220 autoInitSpan.setSucceeded();
1221
1222 LogFlowThisFuncLeave();
1223 return rc;
1224}
1225
1226/**
1227 * Uninitializes this SnapshotMachine object.
1228 */
1229void SnapshotMachine::uninit()
1230{
1231 LogFlowThisFuncEnter();
1232
1233 /* Enclose the state transition Ready->InUninit->NotReady */
1234 AutoUninitSpan autoUninitSpan(this);
1235 if (autoUninitSpan.uninitDone())
1236 return;
1237
1238 uninitDataAndChildObjects();
1239
1240 /* free the essential data structure last */
1241 mData.free();
1242
1243 unconst(mMachine) = NULL;
1244 unconst(mParent) = NULL;
1245 unconst(mPeer) = NULL;
1246
1247 LogFlowThisFuncLeave();
1248}
1249
1250/**
1251 * Overrides VirtualBoxBase::lockHandle() in order to share the lock handle
1252 * with the primary Machine instance (mMachine) if it exists.
1253 */
1254RWLockHandle *SnapshotMachine::lockHandle() const
1255{
1256 AssertReturn(mMachine != NULL, NULL);
1257 return mMachine->lockHandle();
1258}
1259
1260////////////////////////////////////////////////////////////////////////////////
1261//
1262// SnapshotMachine public internal methods
1263//
1264////////////////////////////////////////////////////////////////////////////////
1265
1266/**
1267 * Called by the snapshot object associated with this SnapshotMachine when
1268 * snapshot data such as name or description is changed.
1269 *
1270 * @warning Caller must hold no locks when calling this.
1271 */
1272HRESULT SnapshotMachine::i_onSnapshotChange(Snapshot *aSnapshot)
1273{
1274 AutoMultiWriteLock2 mlock(this, aSnapshot COMMA_LOCKVAL_SRC_POS);
1275 Guid uuidMachine(mData->mUuid),
1276 uuidSnapshot(aSnapshot->i_getId());
1277 bool fNeedsGlobalSaveSettings = false;
1278
1279 /* Flag the machine as dirty or change won't get saved. We disable the
1280 * modification of the current state flag, cause this snapshot data isn't
1281 * related to the current state. */
1282 mMachine->i_setModified(Machine::IsModified_Snapshots, false /* fAllowStateModification */);
1283 HRESULT rc = mMachine->i_saveSettings(&fNeedsGlobalSaveSettings,
1284 SaveS_Force); // we know we need saving, no need to check
1285 mlock.release();
1286
1287 if (SUCCEEDED(rc) && fNeedsGlobalSaveSettings)
1288 {
1289 // save the global settings
1290 AutoWriteLock vboxlock(mParent COMMA_LOCKVAL_SRC_POS);
1291 rc = mParent->i_saveSettings();
1292 }
1293
1294 /* inform callbacks */
1295 mParent->i_onSnapshotChange(uuidMachine, uuidSnapshot);
1296
1297 return rc;
1298}
1299
1300////////////////////////////////////////////////////////////////////////////////
1301//
1302// SessionMachine task records
1303//
1304////////////////////////////////////////////////////////////////////////////////
1305
1306/**
1307 * Still abstract base class for SessionMachine::TakeSnapshotTask,
1308 * SessionMachine::RestoreSnapshotTask and SessionMachine::DeleteSnapshotTask.
1309 */
1310struct SessionMachine::SnapshotTask
1311 : public SessionMachine::Task
1312{
1313 SnapshotTask(SessionMachine *m,
1314 Progress *p,
1315 const Utf8Str &t,
1316 Snapshot *s)
1317 : Task(m, p, t),
1318 m_pSnapshot(s)
1319 {}
1320
1321 ComObjPtr<Snapshot> m_pSnapshot;
1322};
1323
1324/** Take snapshot task */
1325struct SessionMachine::TakeSnapshotTask
1326 : public SessionMachine::SnapshotTask
1327{
1328 TakeSnapshotTask(SessionMachine *m,
1329 Progress *p,
1330 const Utf8Str &t,
1331 Snapshot *s,
1332 const Utf8Str &strName,
1333 const Utf8Str &strDescription,
1334 bool fPause,
1335 uint32_t uMemSize,
1336 bool fTakingSnapshotOnline)
1337 : SnapshotTask(m, p, t, s),
1338 m_strName(strName),
1339 m_strDescription(strDescription),
1340 m_fPause(fPause),
1341 m_uMemSize(uMemSize),
1342 m_fTakingSnapshotOnline(fTakingSnapshotOnline)
1343 {
1344 if (fTakingSnapshotOnline)
1345 m_pDirectControl = m->mData->mSession.mDirectControl;
1346 }
1347
1348 void handler()
1349 {
1350 ((SessionMachine *)(Machine *)m_pMachine)->i_takeSnapshotHandler(*this);
1351 }
1352
1353 Utf8Str m_strName;
1354 Utf8Str m_strDescription;
1355 Utf8Str m_strStateFilePath;
1356 ComPtr<IInternalSessionControl> m_pDirectControl;
1357 bool m_fPause;
1358 uint32_t m_uMemSize;
1359 bool m_fTakingSnapshotOnline;
1360};
1361
1362/** Restore snapshot task */
1363struct SessionMachine::RestoreSnapshotTask
1364 : public SessionMachine::SnapshotTask
1365{
1366 RestoreSnapshotTask(SessionMachine *m,
1367 Progress *p,
1368 const Utf8Str &t,
1369 Snapshot *s)
1370 : SnapshotTask(m, p, t, s)
1371 {}
1372
1373 void handler()
1374 {
1375 ((SessionMachine *)(Machine *)m_pMachine)->i_restoreSnapshotHandler(*this);
1376 }
1377};
1378
1379/** Delete snapshot task */
1380struct SessionMachine::DeleteSnapshotTask
1381 : public SessionMachine::SnapshotTask
1382{
1383 DeleteSnapshotTask(SessionMachine *m,
1384 Progress *p,
1385 const Utf8Str &t,
1386 bool fDeleteOnline,
1387 Snapshot *s)
1388 : SnapshotTask(m, p, t, s),
1389 m_fDeleteOnline(fDeleteOnline)
1390 {}
1391
1392 void handler()
1393 {
1394 ((SessionMachine *)(Machine *)m_pMachine)->i_deleteSnapshotHandler(*this);
1395 }
1396
1397 bool m_fDeleteOnline;
1398};
1399
1400
1401////////////////////////////////////////////////////////////////////////////////
1402//
1403// TakeSnapshot methods (Machine and related tasks)
1404//
1405////////////////////////////////////////////////////////////////////////////////
1406
1407HRESULT Machine::takeSnapshot(const com::Utf8Str &aName,
1408 const com::Utf8Str &aDescription,
1409 BOOL fPause,
1410 ComPtr<IProgress> &aProgress)
1411{
1412 NOREF(aName);
1413 NOREF(aDescription);
1414 NOREF(fPause);
1415 NOREF(aProgress);
1416 ReturnComNotImplemented();
1417}
1418
1419HRESULT SessionMachine::takeSnapshot(const com::Utf8Str &aName,
1420 const com::Utf8Str &aDescription,
1421 BOOL fPause,
1422 ComPtr<IProgress> &aProgress)
1423{
1424 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1425 LogFlowThisFunc(("aName='%s' mMachineState=%d\n", aName.c_str(), mData->mMachineState));
1426
1427 if (Global::IsTransient(mData->mMachineState))
1428 return setError(VBOX_E_INVALID_VM_STATE,
1429 tr("Cannot take a snapshot of the machine while it is changing the state (machine state: %s)"),
1430 Global::stringifyMachineState(mData->mMachineState));
1431
1432 HRESULT rc = S_OK;
1433
1434 // prepare the progress object:
1435 // a) count the no. of hard disk attachments to get a matching no. of progress sub-operations
1436 ULONG cOperations = 2; // always at least setting up + finishing up
1437 ULONG ulTotalOperationsWeight = 2; // one each for setting up + finishing up
1438
1439 for (MediaData::AttachmentList::iterator it = mMediaData->mAttachments.begin();
1440 it != mMediaData->mAttachments.end();
1441 ++it)
1442 {
1443 const ComObjPtr<MediumAttachment> pAtt(*it);
1444 AutoReadLock attlock(pAtt COMMA_LOCKVAL_SRC_POS);
1445 AutoCaller attCaller(pAtt);
1446 if (pAtt->i_getType() == DeviceType_HardDisk)
1447 {
1448 ++cOperations;
1449
1450 // assume that creating a diff image takes as long as saving a 1MB state
1451 ulTotalOperationsWeight += 1;
1452 }
1453 }
1454
1455 // b) one extra sub-operations for online snapshots OR offline snapshots that have a saved state (needs to be copied)
1456 const bool fTakingSnapshotOnline = Global::IsOnline(mData->mMachineState);
1457 LogFlowThisFunc(("fTakingSnapshotOnline = %d\n", fTakingSnapshotOnline));
1458 if (fTakingSnapshotOnline)
1459 {
1460 ++cOperations;
1461 ulTotalOperationsWeight += mHWData->mMemorySize;
1462 }
1463
1464 // finally, create the progress object
1465 ComObjPtr<Progress> pProgress;
1466 pProgress.createObject();
1467 rc = pProgress->init(mParent,
1468 static_cast<IMachine *>(this),
1469 Bstr(tr("Taking a snapshot of the virtual machine")).raw(),
1470 fTakingSnapshotOnline /* aCancelable */,
1471 cOperations,
1472 ulTotalOperationsWeight,
1473 Bstr(tr("Setting up snapshot operation")).raw(), // first sub-op description
1474 1); // ulFirstOperationWeight
1475 if (FAILED(rc))
1476 return rc;
1477
1478 /* create and start the task on a separate thread (note that it will not
1479 * start working until we release alock) */
1480 TakeSnapshotTask *pTask = new TakeSnapshotTask(this,
1481 pProgress,
1482 "TakeSnap",
1483 NULL /* pSnapshot */,
1484 aName,
1485 aDescription,
1486 !!fPause,
1487 mHWData->mMemorySize,
1488 fTakingSnapshotOnline);
1489 rc = pTask->createThread();
1490 if (FAILED(rc))
1491 return rc;
1492
1493 /* set the proper machine state (note: after creating a Task instance) */
1494 if (fTakingSnapshotOnline)
1495 {
1496 if (pTask->m_machineStateBackup != MachineState_Paused && !fPause)
1497 i_setMachineState(MachineState_LiveSnapshotting);
1498 else
1499 i_setMachineState(MachineState_OnlineSnapshotting);
1500 i_updateMachineStateOnClient();
1501 }
1502 else
1503 i_setMachineState(MachineState_Snapshotting);
1504
1505 pTask->m_pProgress.queryInterfaceTo(aProgress.asOutParam());
1506
1507 return rc;
1508}
1509
1510/**
1511 * Task thread implementation for SessionMachine::TakeSnapshot(), called from
1512 * SessionMachine::taskHandler().
1513 *
1514 * @note Locks this object for writing.
1515 *
1516 * @param task
1517 * @return
1518 */
1519void SessionMachine::i_takeSnapshotHandler(TakeSnapshotTask &task)
1520{
1521 LogFlowThisFuncEnter();
1522
1523 // Taking a snapshot consists of the following:
1524 // 1) creating a Snapshot object with the current state of the machine
1525 // (hardware + storage)
1526 // 2) creating a diff image for each virtual hard disk, into which write
1527 // operations go after the snapshot has been created
1528 // 3) if the machine is online: saving the state of the virtual machine
1529 // (in the VM process)
1530 // 4) reattach the hard disks
1531 // 5) update the various snapshot/machine objects, save settings
1532
1533 HRESULT rc = S_OK;
1534 AutoCaller autoCaller(this);
1535 LogFlowThisFunc(("state=%d\n", getObjectState().getState()));
1536 if (FAILED(autoCaller.rc()))
1537 {
1538 /* we might have been uninitialized because the session was accidentally
1539 * closed by the client, so don't assert */
1540 rc = setError(E_FAIL,
1541 tr("The session has been accidentally closed"));
1542 task.m_pProgress->i_notifyComplete(rc);
1543 LogFlowThisFuncLeave();
1544 return;
1545 }
1546
1547 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1548
1549 bool fBeganTakingSnapshot = false;
1550 BOOL fSuspendedBySave = FALSE;
1551
1552 try
1553 {
1554 // @todo: at this point we have to be in the right state!!!!
1555 AssertStmt( !Global::IsOnlineOrTransient(mData->mMachineState)
1556 || mData->mMachineState == MachineState_Snapshotting
1557 || mData->mMachineState == MachineState_OnlineSnapshotting
1558 || mData->mMachineState == MachineState_LiveSnapshotting, throw E_FAIL);
1559 AssertStmt(task.m_machineStateBackup != mData->mMachineState, throw E_FAIL);
1560 AssertStmt(task.m_pSnapshot.isNull(), throw E_FAIL);
1561
1562 if ( mData->mCurrentSnapshot
1563 && mData->mCurrentSnapshot->i_getDepth() >= SETTINGS_SNAPSHOT_DEPTH_MAX)
1564 {
1565 throw setError(VBOX_E_INVALID_OBJECT_STATE,
1566 tr("Cannot take another snapshot for machine '%s', because it exceeds the maximum snapshot depth limit. Please delete some earlier snapshot which you no longer need"),
1567 mUserData->s.strName.c_str());
1568 }
1569
1570 if ( !task.m_fTakingSnapshotOnline
1571 && mData->mMachineState != MachineState_Saved)
1572 {
1573 /* save settings to ensure current changes are committed and
1574 * hard disks are fixed up */
1575 rc = i_saveSettings(NULL);
1576 // no need to check for whether VirtualBox.xml needs changing since
1577 // we can't have a machine XML rename pending at this point
1578 if (FAILED(rc))
1579 throw rc;
1580 }
1581
1582 /* task.m_strStateFilePath is "" when the machine is offline or saved */
1583 if (task.m_fTakingSnapshotOnline)
1584 {
1585 Bstr value;
1586 HRESULT rc = GetExtraData(Bstr("VBoxInternal2/ForceTakeSnapshotWithoutState").raw(),
1587 value.asOutParam());
1588 if (FAILED(rc) || value != "1")
1589 // creating a new online snapshot: we need a fresh saved state file
1590 i_composeSavedStateFilename(task.m_strStateFilePath);
1591 }
1592 else if (mData->mMachineState == MachineState_Saved)
1593 // taking an offline snapshot from machine in "saved" state: use existing state file
1594 task.m_strStateFilePath = mSSData->strStateFilePath;
1595
1596 if (task.m_strStateFilePath.isNotEmpty())
1597 {
1598 // ensure the directory for the saved state file exists
1599 rc = VirtualBox::i_ensureFilePathExists(task.m_strStateFilePath, true /* fCreate */);
1600 if (FAILED(rc))
1601 throw rc;
1602 }
1603
1604 /* STEP 1: create the snapshot object */
1605
1606 /* create an ID for the snapshot */
1607 Guid snapshotId;
1608 snapshotId.create();
1609
1610 /* create a snapshot machine object */
1611 ComObjPtr<SnapshotMachine> pSnapshotMachine;
1612 pSnapshotMachine.createObject();
1613 rc = pSnapshotMachine->init(this, snapshotId.ref(), task.m_strStateFilePath);
1614 AssertComRCThrowRC(rc);
1615
1616 /* create a snapshot object */
1617 RTTIMESPEC time;
1618 RTTimeNow(&time);
1619 task.m_pSnapshot.createObject();
1620 rc = task.m_pSnapshot->init(mParent,
1621 snapshotId,
1622 task.m_strName,
1623 task.m_strDescription,
1624 time,
1625 pSnapshotMachine,
1626 mData->mCurrentSnapshot);
1627 AssertComRCThrowRC(rc);
1628
1629 /* STEP 2: create the diff images */
1630 LogFlowThisFunc(("Creating differencing hard disks (online=%d)...\n",
1631 task.m_fTakingSnapshotOnline));
1632
1633 // Backup the media data so we can recover if something goes wrong.
1634 // The matching commit() is in fixupMedia() during SessionMachine::i_finishTakingSnapshot()
1635 i_setModified(IsModified_Storage);
1636 mMediaData.backup();
1637
1638 alock.release();
1639 /* create new differencing hard disks and attach them to this machine */
1640 rc = i_createImplicitDiffs(task.m_pProgress,
1641 1, // operation weight; must be the same as in Machine::TakeSnapshot()
1642 task.m_fTakingSnapshotOnline);
1643 if (FAILED(rc))
1644 throw rc;
1645 alock.acquire();
1646
1647 // MUST NOT save the settings or the media registry here, because
1648 // this causes trouble with rolling back settings if the user cancels
1649 // taking the snapshot after the diff images have been created.
1650
1651 fBeganTakingSnapshot = true;
1652
1653 /* Check sanity: for offline snapshots there must not be a saved state
1654 * file name. All other combinations are valid (though online snapshots
1655 * without saved state file seems inconsistent - there are exotic use
1656 * cases, which need to be explicitly enabled, see the code above. */
1657 if ( !task.m_fTakingSnapshotOnline
1658 && !task.m_strStateFilePath.isEmpty())
1659 throw setError(E_FAIL, "Invalid state of saved state file");
1660
1661 // STEP 3: save the VM state (if online)
1662 if (task.m_fTakingSnapshotOnline)
1663 {
1664 task.m_pProgress->SetNextOperation(Bstr(tr("Saving the machine state")).raw(),
1665 mHWData->mMemorySize); // operation weight, same as computed
1666 // when setting up progress object
1667
1668 if (task.m_strStateFilePath.isNotEmpty())
1669 {
1670 alock.release();
1671 task.m_pProgress->i_setCancelCallback(i_takeSnapshotProgressCancelCallback, &task);
1672 rc = task.m_pDirectControl->SaveStateWithReason(Reason_Snapshot,
1673 task.m_pProgress,
1674 Bstr(task.m_strStateFilePath).raw(),
1675 task.m_fPause,
1676 &fSuspendedBySave);
1677 task.m_pProgress->i_setCancelCallback(NULL, NULL);
1678 alock.acquire();
1679 if (FAILED(rc))
1680 throw rc;
1681 }
1682 else
1683 LogRel(("Machine: skipped saving state as part of online snapshot\n"));
1684
1685 if (!task.m_pProgress->i_notifyPointOfNoReturn())
1686 throw setError(E_FAIL, tr("Canceled"));
1687
1688 // STEP 4: reattach hard disks
1689 LogFlowThisFunc(("Reattaching new differencing hard disks...\n"));
1690
1691 task.m_pProgress->SetNextOperation(Bstr(tr("Reconfiguring medium attachments")).raw(),
1692 1); // operation weight, same as computed when setting up progress object
1693
1694 com::SafeIfaceArray<IMediumAttachment> atts;
1695 rc = COMGETTER(MediumAttachments)(ComSafeArrayAsOutParam(atts));
1696 if (FAILED(rc))
1697 throw rc;
1698
1699 alock.release();
1700 rc = task.m_pDirectControl->ReconfigureMediumAttachments(ComSafeArrayAsInParam(atts));
1701 alock.acquire();
1702 if (FAILED(rc))
1703 throw rc;
1704 }
1705
1706 /*
1707 * Finalize the requested snapshot object. This will reset the
1708 * machine state to the state it had at the beginning.
1709 */
1710 rc = i_finishTakingSnapshot(task, alock, true /*aSuccess*/);
1711 // do not throw rc here because we can't call i_finishTakingSnapshot() twice
1712 LogFlowThisFunc(("i_finishTakingSnapshot -> %Rhrc [mMachineState=%s]\n", rc, Global::stringifyMachineState(mData->mMachineState)));
1713 }
1714 catch (HRESULT rcThrown)
1715 {
1716 rc = rcThrown;
1717 LogThisFunc(("Caught %Rhrc [mMachineState=%s]\n", rc, Global::stringifyMachineState(mData->mMachineState)));
1718
1719 // @todo r=klaus check that the implicit diffs created above are cleaned up im the relevant error cases
1720
1721 /* preserve existing error info */
1722 ErrorInfoKeeper eik;
1723
1724 if (fBeganTakingSnapshot)
1725 i_finishTakingSnapshot(task, alock, false /*aSuccess*/);
1726
1727 // have to postpone this to the end as i_finishTakingSnapshot() needs
1728 // it for various cleanup steps
1729 task.m_pSnapshot->uninit();
1730 task.m_pSnapshot.setNull();
1731 }
1732 Assert(alock.isWriteLockOnCurrentThread());
1733
1734 {
1735 // Keep all error information over the cleanup steps
1736 ErrorInfoKeeper eik;
1737
1738 /*
1739 * Fix up the machine state.
1740 *
1741 * For offline snapshots we just update the local copy, for the other
1742 * variants do the entire work. This ensures that the state is in sync
1743 * with the VM process (in particular the VM execution state).
1744 */
1745 bool fNeedClientMachineStateUpdate = false;
1746 if ( mData->mMachineState == MachineState_LiveSnapshotting
1747 || mData->mMachineState == MachineState_OnlineSnapshotting
1748 || mData->mMachineState == MachineState_Snapshotting)
1749 {
1750 if (!task.m_fTakingSnapshotOnline)
1751 i_setMachineState(task.m_machineStateBackup);
1752 else
1753 {
1754 MachineState_T enmMachineState = MachineState_Null;
1755 HRESULT rc2 = task.m_pDirectControl->COMGETTER(NominalState)(&enmMachineState);
1756 if (FAILED(rc2) || enmMachineState == MachineState_Null)
1757 {
1758 AssertMsgFailed(("state=%s\n", Global::stringifyMachineState(enmMachineState)));
1759 // pure nonsense, try to continue somehow
1760 enmMachineState = MachineState_Aborted;
1761 }
1762 if (enmMachineState == MachineState_Paused)
1763 {
1764 if (fSuspendedBySave)
1765 {
1766 alock.release();
1767 rc2 = task.m_pDirectControl->ResumeWithReason(Reason_Snapshot);
1768 alock.acquire();
1769 if (SUCCEEDED(rc2))
1770 enmMachineState = task.m_machineStateBackup;
1771 }
1772 else
1773 enmMachineState = task.m_machineStateBackup;
1774 }
1775 if (enmMachineState != mData->mMachineState)
1776 {
1777 fNeedClientMachineStateUpdate = true;
1778 i_setMachineState(enmMachineState);
1779 }
1780 }
1781 }
1782
1783 /* check the remote state to see that we got it right. */
1784 MachineState_T enmMachineState = MachineState_Null;
1785 if (!task.m_pDirectControl.isNull())
1786 {
1787 ComPtr<IConsole> pConsole;
1788 task.m_pDirectControl->COMGETTER(RemoteConsole)(pConsole.asOutParam());
1789 if (!pConsole.isNull())
1790 pConsole->COMGETTER(State)(&enmMachineState);
1791 }
1792 LogFlowThisFunc(("local mMachineState=%s remote mMachineState=%s\n",
1793 Global::stringifyMachineState(mData->mMachineState),
1794 Global::stringifyMachineState(enmMachineState)));
1795
1796 if (fNeedClientMachineStateUpdate)
1797 i_updateMachineStateOnClient();
1798 }
1799
1800 task.m_pProgress->i_notifyComplete(rc);
1801
1802 LogFlowThisFuncLeave();
1803}
1804
1805
1806/**
1807 * Progress cancelation callback employed by SessionMachine::i_takeSnapshotHandler.
1808 */
1809/*static*/
1810void SessionMachine::i_takeSnapshotProgressCancelCallback(void *pvUser)
1811{
1812 TakeSnapshotTask *pTask = (TakeSnapshotTask *)pvUser;
1813 AssertPtrReturnVoid(pTask);
1814 AssertReturnVoid(!pTask->m_pDirectControl.isNull());
1815 pTask->m_pDirectControl->CancelSaveStateWithReason();
1816}
1817
1818
1819/**
1820 * Called by the Console when it's done saving the VM state into the snapshot
1821 * (if online) and reconfiguring the hard disks. See BeginTakingSnapshot() above.
1822 *
1823 * This also gets called if the console part of snapshotting failed after the
1824 * BeginTakingSnapshot() call, to clean up the server side.
1825 *
1826 * @note Locks VirtualBox and this object for writing.
1827 *
1828 * @param aSuccess Whether Console was successful with the client-side snapshot things.
1829 * @return
1830 */
1831HRESULT SessionMachine::i_finishTakingSnapshot(TakeSnapshotTask &task, AutoWriteLock &alock, bool aSuccess)
1832{
1833 LogFlowThisFunc(("\n"));
1834
1835 Assert(alock.isWriteLockOnCurrentThread());
1836
1837 AssertReturn( !aSuccess
1838 || mData->mMachineState == MachineState_Snapshotting
1839 || mData->mMachineState == MachineState_OnlineSnapshotting
1840 || mData->mMachineState == MachineState_LiveSnapshotting, E_FAIL);
1841
1842 ComObjPtr<Snapshot> pOldFirstSnap = mData->mFirstSnapshot;
1843 ComObjPtr<Snapshot> pOldCurrentSnap = mData->mCurrentSnapshot;
1844
1845 bool fOnline = Global::IsOnline(task.m_machineStateBackup);
1846
1847 HRESULT rc = S_OK;
1848
1849 if (aSuccess)
1850 {
1851 // new snapshot becomes the current one
1852 mData->mCurrentSnapshot = task.m_pSnapshot;
1853
1854 /* memorize the first snapshot if necessary */
1855 if (!mData->mFirstSnapshot)
1856 mData->mFirstSnapshot = mData->mCurrentSnapshot;
1857
1858 int flSaveSettings = SaveS_Force; // do not do a deep compare in machine settings,
1859 // snapshots change, so we know we need to save
1860 if (!fOnline)
1861 /* the machine was powered off or saved when taking a snapshot, so
1862 * reset the mCurrentStateModified flag */
1863 flSaveSettings |= SaveS_ResetCurStateModified;
1864
1865 rc = i_saveSettings(NULL, flSaveSettings);
1866 }
1867
1868 if (aSuccess && SUCCEEDED(rc))
1869 {
1870 /* associate old hard disks with the snapshot and do locking/unlocking*/
1871 i_commitMedia(fOnline);
1872
1873 /* inform callbacks */
1874 mParent->i_onSnapshotTaken(mData->mUuid,
1875 task.m_pSnapshot->i_getId());
1876 alock.release();
1877 }
1878 else
1879 {
1880 /* delete all differencing hard disks created (this will also attach
1881 * their parents back by rolling back mMediaData) */
1882 alock.release();
1883
1884 i_rollbackMedia();
1885
1886 mData->mFirstSnapshot = pOldFirstSnap; // might have been changed above
1887 mData->mCurrentSnapshot = pOldCurrentSnap; // might have been changed above
1888
1889 // delete the saved state file (it might have been already created)
1890 if (fOnline)
1891 // no need to test for whether the saved state file is shared: an online
1892 // snapshot means that a new saved state file was created, which we must
1893 // clean up now
1894 RTFileDelete(task.m_pSnapshot->i_getStateFilePath().c_str());
1895
1896 alock.acquire();
1897
1898 task.m_pSnapshot->uninit();
1899 alock.release();
1900
1901 }
1902
1903 /* clear out the snapshot data */
1904 task.m_pSnapshot.setNull();
1905
1906 /* alock has been released already */
1907 mParent->i_saveModifiedRegistries();
1908
1909 alock.acquire();
1910
1911 return rc;
1912}
1913
1914////////////////////////////////////////////////////////////////////////////////
1915//
1916// RestoreSnapshot methods (Machine and related tasks)
1917//
1918////////////////////////////////////////////////////////////////////////////////
1919
1920HRESULT Machine::restoreSnapshot(const ComPtr<ISnapshot> &aSnapshot,
1921 ComPtr<IProgress> &aProgress)
1922{
1923 NOREF(aSnapshot);
1924 NOREF(aProgress);
1925 ReturnComNotImplemented();
1926}
1927
1928/**
1929 * Restoring a snapshot happens entirely on the server side, the machine cannot be running.
1930 *
1931 * This creates a new thread that does the work and returns a progress object to the client.
1932 * Actual work then takes place in RestoreSnapshotTask::handler().
1933 *
1934 * @note Locks this + children objects for writing!
1935 *
1936 * @param aSnapshot in: the snapshot to restore.
1937 * @param aProgress out: progress object to monitor restore thread.
1938 * @return
1939 */
1940HRESULT SessionMachine::restoreSnapshot(const ComPtr<ISnapshot> &aSnapshot,
1941 ComPtr<IProgress> &aProgress)
1942{
1943 LogFlowThisFuncEnter();
1944
1945 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1946
1947 // machine must not be running
1948 if (Global::IsOnlineOrTransient(mData->mMachineState))
1949 return setError(VBOX_E_INVALID_VM_STATE,
1950 tr("Cannot delete the current state of the running machine (machine state: %s)"),
1951 Global::stringifyMachineState(mData->mMachineState));
1952
1953 ISnapshot* iSnapshot = aSnapshot;
1954 ComObjPtr<Snapshot> pSnapshot(static_cast<Snapshot*>(iSnapshot));
1955 ComObjPtr<SnapshotMachine> pSnapMachine = pSnapshot->i_getSnapshotMachine();
1956
1957 HRESULT rc = S_OK;
1958
1959 // create a progress object. The number of operations is:
1960 // 1 (preparing) + # of hard disks + 1 (if we need to copy the saved state file) */
1961 LogFlowThisFunc(("Going thru snapshot machine attachments to determine progress setup\n"));
1962
1963 ULONG ulOpCount = 1; // one for preparations
1964 ULONG ulTotalWeight = 1; // one for preparations
1965 for (MediaData::AttachmentList::iterator it = pSnapMachine->mMediaData->mAttachments.begin();
1966 it != pSnapMachine->mMediaData->mAttachments.end();
1967 ++it)
1968 {
1969 ComObjPtr<MediumAttachment> &pAttach = *it;
1970 AutoReadLock attachLock(pAttach COMMA_LOCKVAL_SRC_POS);
1971 if (pAttach->i_getType() == DeviceType_HardDisk)
1972 {
1973 ++ulOpCount;
1974 ++ulTotalWeight; // assume one MB weight for each differencing hard disk to manage
1975 Assert(pAttach->i_getMedium());
1976 LogFlowThisFunc(("op %d: considering hard disk attachment %s\n", ulOpCount,
1977 pAttach->i_getMedium()->i_getName().c_str()));
1978 }
1979 }
1980
1981 ComObjPtr<Progress> pProgress;
1982 pProgress.createObject();
1983 pProgress->init(mParent, static_cast<IMachine*>(this),
1984 BstrFmt(tr("Restoring snapshot '%s'"), pSnapshot->i_getName().c_str()).raw(),
1985 FALSE /* aCancelable */,
1986 ulOpCount,
1987 ulTotalWeight,
1988 Bstr(tr("Restoring machine settings")).raw(),
1989 1);
1990
1991 /* create and start the task on a separate thread (note that it will not
1992 * start working until we release alock) */
1993 RestoreSnapshotTask *pTask = new RestoreSnapshotTask(this,
1994 pProgress,
1995 "RestoreSnap",
1996 pSnapshot);
1997 rc = pTask->createThread();
1998 if (FAILED(rc))
1999 return rc;
2000
2001 /* set the proper machine state (note: after creating a Task instance) */
2002 i_setMachineState(MachineState_RestoringSnapshot);
2003
2004 /* return the progress to the caller */
2005 pProgress.queryInterfaceTo(aProgress.asOutParam());
2006
2007 LogFlowThisFuncLeave();
2008
2009 return S_OK;
2010}
2011
2012/**
2013 * Worker method for the restore snapshot thread created by SessionMachine::RestoreSnapshot().
2014 * This method gets called indirectly through SessionMachine::taskHandler() which then
2015 * calls RestoreSnapshotTask::handler().
2016 *
2017 * The RestoreSnapshotTask contains the progress object returned to the console by
2018 * SessionMachine::RestoreSnapshot, through which progress and results are reported.
2019 *
2020 * @note Locks mParent + this object for writing.
2021 *
2022 * @param pTask Task data.
2023 */
2024void SessionMachine::i_restoreSnapshotHandler(RestoreSnapshotTask &task)
2025{
2026 LogFlowThisFuncEnter();
2027
2028 AutoCaller autoCaller(this);
2029
2030 LogFlowThisFunc(("state=%d\n", getObjectState().getState()));
2031 if (!autoCaller.isOk())
2032 {
2033 /* we might have been uninitialized because the session was accidentally
2034 * closed by the client, so don't assert */
2035 task.m_pProgress->i_notifyComplete(E_FAIL,
2036 COM_IIDOF(IMachine),
2037 getComponentName(),
2038 tr("The session has been accidentally closed"));
2039
2040 LogFlowThisFuncLeave();
2041 return;
2042 }
2043
2044 HRESULT rc = S_OK;
2045
2046 bool stateRestored = false;
2047
2048 try
2049 {
2050 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
2051
2052 /* Discard all current changes to mUserData (name, OSType etc.).
2053 * Note that the machine is powered off, so there is no need to inform
2054 * the direct session. */
2055 if (mData->flModifications)
2056 i_rollback(false /* aNotify */);
2057
2058 /* Delete the saved state file if the machine was Saved prior to this
2059 * operation */
2060 if (task.m_machineStateBackup == MachineState_Saved)
2061 {
2062 Assert(!mSSData->strStateFilePath.isEmpty());
2063
2064 // release the saved state file AFTER unsetting the member variable
2065 // so that releaseSavedStateFile() won't think it's still in use
2066 Utf8Str strStateFile(mSSData->strStateFilePath);
2067 mSSData->strStateFilePath.setNull();
2068 i_releaseSavedStateFile(strStateFile, NULL /* pSnapshotToIgnore */ );
2069
2070 task.modifyBackedUpState(MachineState_PoweredOff);
2071
2072 rc = i_saveStateSettings(SaveSTS_StateFilePath);
2073 if (FAILED(rc))
2074 throw rc;
2075 }
2076
2077 RTTIMESPEC snapshotTimeStamp;
2078 RTTimeSpecSetMilli(&snapshotTimeStamp, 0);
2079
2080 {
2081 AutoReadLock snapshotLock(task.m_pSnapshot COMMA_LOCKVAL_SRC_POS);
2082
2083 /* remember the timestamp of the snapshot we're restoring from */
2084 snapshotTimeStamp = task.m_pSnapshot->i_getTimeStamp();
2085
2086 ComPtr<SnapshotMachine> pSnapshotMachine(task.m_pSnapshot->i_getSnapshotMachine());
2087
2088 /* copy all hardware data from the snapshot */
2089 i_copyFrom(pSnapshotMachine);
2090
2091 LogFlowThisFunc(("Restoring hard disks from the snapshot...\n"));
2092
2093 // restore the attachments from the snapshot
2094 i_setModified(IsModified_Storage);
2095 mMediaData.backup();
2096 mMediaData->mAttachments.clear();
2097 for (MediaData::AttachmentList::const_iterator it = pSnapshotMachine->mMediaData->mAttachments.begin();
2098 it != pSnapshotMachine->mMediaData->mAttachments.end();
2099 ++it)
2100 {
2101 ComObjPtr<MediumAttachment> pAttach;
2102 pAttach.createObject();
2103 pAttach->initCopy(this, *it);
2104 mMediaData->mAttachments.push_back(pAttach);
2105 }
2106
2107 /* release the locks before the potentially lengthy operation */
2108 snapshotLock.release();
2109 alock.release();
2110
2111 rc = i_createImplicitDiffs(task.m_pProgress,
2112 1,
2113 false /* aOnline */);
2114 if (FAILED(rc))
2115 throw rc;
2116
2117 alock.acquire();
2118 snapshotLock.acquire();
2119
2120 /* Note: on success, current (old) hard disks will be
2121 * deassociated/deleted on #commit() called from #i_saveSettings() at
2122 * the end. On failure, newly created implicit diffs will be
2123 * deleted by #rollback() at the end. */
2124
2125 /* should not have a saved state file associated at this point */
2126 Assert(mSSData->strStateFilePath.isEmpty());
2127
2128 const Utf8Str &strSnapshotStateFile = task.m_pSnapshot->i_getStateFilePath();
2129
2130 if (strSnapshotStateFile.isNotEmpty())
2131 // online snapshot: then share the state file
2132 mSSData->strStateFilePath = strSnapshotStateFile;
2133
2134 LogFlowThisFunc(("Setting new current snapshot {%RTuuid}\n", task.m_pSnapshot->i_getId().raw()));
2135 /* make the snapshot we restored from the current snapshot */
2136 mData->mCurrentSnapshot = task.m_pSnapshot;
2137 }
2138
2139 /* grab differencing hard disks from the old attachments that will
2140 * become unused and need to be auto-deleted */
2141 std::list< ComObjPtr<MediumAttachment> > llDiffAttachmentsToDelete;
2142
2143 for (MediaData::AttachmentList::const_iterator it = mMediaData.backedUpData()->mAttachments.begin();
2144 it != mMediaData.backedUpData()->mAttachments.end();
2145 ++it)
2146 {
2147 ComObjPtr<MediumAttachment> pAttach = *it;
2148 ComObjPtr<Medium> pMedium = pAttach->i_getMedium();
2149
2150 /* while the hard disk is attached, the number of children or the
2151 * parent cannot change, so no lock */
2152 if ( !pMedium.isNull()
2153 && pAttach->i_getType() == DeviceType_HardDisk
2154 && !pMedium->i_getParent().isNull()
2155 && pMedium->i_getChildren().size() == 0
2156 )
2157 {
2158 LogFlowThisFunc(("Picked differencing image '%s' for deletion\n", pMedium->i_getName().c_str()));
2159
2160 llDiffAttachmentsToDelete.push_back(pAttach);
2161 }
2162 }
2163
2164 /* we have already deleted the current state, so set the execution
2165 * state accordingly no matter of the delete snapshot result */
2166 if (mSSData->strStateFilePath.isNotEmpty())
2167 i_setMachineState(MachineState_Saved);
2168 else
2169 i_setMachineState(MachineState_PoweredOff);
2170
2171 i_updateMachineStateOnClient();
2172 stateRestored = true;
2173
2174 /* Paranoia: no one must have saved the settings in the mean time. If
2175 * it happens nevertheless we'll close our eyes and continue below. */
2176 Assert(mMediaData.isBackedUp());
2177
2178 /* assign the timestamp from the snapshot */
2179 Assert(RTTimeSpecGetMilli(&snapshotTimeStamp) != 0);
2180 mData->mLastStateChange = snapshotTimeStamp;
2181
2182 // detach the current-state diffs that we detected above and build a list of
2183 // image files to delete _after_ i_saveSettings()
2184
2185 MediaList llDiffsToDelete;
2186
2187 for (std::list< ComObjPtr<MediumAttachment> >::iterator it = llDiffAttachmentsToDelete.begin();
2188 it != llDiffAttachmentsToDelete.end();
2189 ++it)
2190 {
2191 ComObjPtr<MediumAttachment> pAttach = *it; // guaranteed to have only attachments where medium != NULL
2192 ComObjPtr<Medium> pMedium = pAttach->i_getMedium();
2193
2194 AutoWriteLock mlock(pMedium COMMA_LOCKVAL_SRC_POS);
2195
2196 LogFlowThisFunc(("Detaching old current state in differencing image '%s'\n", pMedium->i_getName().c_str()));
2197
2198 // Normally we "detach" the medium by removing the attachment object
2199 // from the current machine data; i_saveSettings() below would then
2200 // compare the current machine data with the one in the backup
2201 // and actually call Medium::removeBackReference(). But that works only half
2202 // the time in our case so instead we force a detachment here:
2203 // remove from machine data
2204 mMediaData->mAttachments.remove(pAttach);
2205 // Remove it from the backup or else i_saveSettings will try to detach
2206 // it again and assert. The paranoia check avoids crashes (see
2207 // assert above) if this code is buggy and saves settings in the
2208 // wrong place.
2209 if (mMediaData.isBackedUp())
2210 mMediaData.backedUpData()->mAttachments.remove(pAttach);
2211 // then clean up backrefs
2212 pMedium->i_removeBackReference(mData->mUuid);
2213
2214 llDiffsToDelete.push_back(pMedium);
2215 }
2216
2217 // save machine settings, reset the modified flag and commit;
2218 bool fNeedsGlobalSaveSettings = false;
2219 rc = i_saveSettings(&fNeedsGlobalSaveSettings,
2220 SaveS_ResetCurStateModified);
2221 if (FAILED(rc))
2222 throw rc;
2223
2224 // release the locks before updating registry and deleting image files
2225 alock.release();
2226
2227 // unconditionally add the parent registry.
2228 mParent->i_markRegistryModified(mParent->i_getGlobalRegistryId());
2229
2230 // from here on we cannot roll back on failure any more
2231
2232 for (MediaList::iterator it = llDiffsToDelete.begin();
2233 it != llDiffsToDelete.end();
2234 ++it)
2235 {
2236 ComObjPtr<Medium> &pMedium = *it;
2237 LogFlowThisFunc(("Deleting old current state in differencing image '%s'\n", pMedium->i_getName().c_str()));
2238
2239 HRESULT rc2 = pMedium->i_deleteStorage(NULL /* aProgress */,
2240 true /* aWait */);
2241 // ignore errors here because we cannot roll back after i_saveSettings() above
2242 if (SUCCEEDED(rc2))
2243 pMedium->uninit();
2244 }
2245 }
2246 catch (HRESULT aRC)
2247 {
2248 rc = aRC;
2249 }
2250
2251 if (FAILED(rc))
2252 {
2253 /* preserve existing error info */
2254 ErrorInfoKeeper eik;
2255
2256 /* undo all changes on failure */
2257 i_rollback(false /* aNotify */);
2258
2259 if (!stateRestored)
2260 {
2261 /* restore the machine state */
2262 i_setMachineState(task.m_machineStateBackup);
2263 i_updateMachineStateOnClient();
2264 }
2265 }
2266
2267 mParent->i_saveModifiedRegistries();
2268
2269 /* set the result (this will try to fetch current error info on failure) */
2270 task.m_pProgress->i_notifyComplete(rc);
2271
2272 if (SUCCEEDED(rc))
2273 mParent->i_onSnapshotDeleted(mData->mUuid, Guid());
2274
2275 LogFlowThisFunc(("Done restoring snapshot (rc=%08X)\n", rc));
2276
2277 LogFlowThisFuncLeave();
2278}
2279
2280////////////////////////////////////////////////////////////////////////////////
2281//
2282// DeleteSnapshot methods (SessionMachine and related tasks)
2283//
2284////////////////////////////////////////////////////////////////////////////////
2285
2286HRESULT Machine::deleteSnapshot(const com::Guid &aId, ComPtr<IProgress> &aProgress)
2287{
2288 NOREF(aId);
2289 NOREF(aProgress);
2290 ReturnComNotImplemented();
2291}
2292
2293HRESULT SessionMachine::deleteSnapshot(const com::Guid &aId, ComPtr<IProgress> &aProgress)
2294{
2295 return i_deleteSnapshot(aId, aId,
2296 FALSE /* fDeleteAllChildren */,
2297 aProgress);
2298}
2299
2300HRESULT Machine::deleteSnapshotAndAllChildren(const com::Guid &aId, ComPtr<IProgress> &aProgress)
2301{
2302 NOREF(aId);
2303 NOREF(aProgress);
2304 ReturnComNotImplemented();
2305}
2306
2307HRESULT SessionMachine::deleteSnapshotAndAllChildren(const com::Guid &aId, ComPtr<IProgress> &aProgress)
2308{
2309 return i_deleteSnapshot(aId, aId,
2310 TRUE /* fDeleteAllChildren */,
2311 aProgress);
2312}
2313
2314HRESULT Machine::deleteSnapshotRange(const com::Guid &aStartId, const com::Guid &aEndId, ComPtr<IProgress> &aProgress)
2315{
2316 NOREF(aStartId);
2317 NOREF(aEndId);
2318 NOREF(aProgress);
2319 ReturnComNotImplemented();
2320}
2321
2322HRESULT SessionMachine::deleteSnapshotRange(const com::Guid &aStartId, const com::Guid &aEndId, ComPtr<IProgress> &aProgress)
2323{
2324 return i_deleteSnapshot(aStartId, aEndId,
2325 FALSE /* fDeleteAllChildren */,
2326 aProgress);
2327}
2328
2329
2330/**
2331 * Implementation for SessionMachine::i_deleteSnapshot().
2332 *
2333 * Gets called from SessionMachine::DeleteSnapshot(). Deleting a snapshot
2334 * happens entirely on the server side if the machine is not running, and
2335 * if it is running then the merges are done via internal session callbacks.
2336 *
2337 * This creates a new thread that does the work and returns a progress
2338 * object to the client.
2339 *
2340 * Actual work then takes place in SessionMachine::i_deleteSnapshotHandler().
2341 *
2342 * @note Locks mParent + this + children objects for writing!
2343 */
2344HRESULT SessionMachine::i_deleteSnapshot(const com::Guid &aStartId,
2345 const com::Guid &aEndId,
2346 BOOL aDeleteAllChildren,
2347 ComPtr<IProgress> &aProgress)
2348{
2349 LogFlowThisFuncEnter();
2350
2351 AssertReturn(!aStartId.isZero() && !aEndId.isZero() && aStartId.isValid() && aEndId.isValid(), E_INVALIDARG);
2352
2353
2354 /** @todo implement the "and all children" and "range" variants */
2355 if (aDeleteAllChildren || aStartId != aEndId)
2356 ReturnComNotImplemented();
2357
2358 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
2359
2360 if (Global::IsTransient(mData->mMachineState))
2361 return setError(VBOX_E_INVALID_VM_STATE,
2362 tr("Cannot delete a snapshot of the machine while it is changing the state (machine state: %s)"),
2363 Global::stringifyMachineState(mData->mMachineState));
2364
2365 // be very picky about machine states
2366 if ( Global::IsOnlineOrTransient(mData->mMachineState)
2367 && mData->mMachineState != MachineState_PoweredOff
2368 && mData->mMachineState != MachineState_Saved
2369 && mData->mMachineState != MachineState_Teleported
2370 && mData->mMachineState != MachineState_Aborted
2371 && mData->mMachineState != MachineState_Running
2372 && mData->mMachineState != MachineState_Paused)
2373 return setError(VBOX_E_INVALID_VM_STATE,
2374 tr("Invalid machine state: %s"),
2375 Global::stringifyMachineState(mData->mMachineState));
2376
2377 ComObjPtr<Snapshot> pSnapshot;
2378 HRESULT rc = i_findSnapshotById(aStartId, pSnapshot, true /* aSetError */);
2379 if (FAILED(rc)) return rc;
2380
2381 AutoWriteLock snapshotLock(pSnapshot COMMA_LOCKVAL_SRC_POS);
2382 Utf8Str str;
2383
2384 size_t childrenCount = pSnapshot->i_getChildrenCount();
2385 if (childrenCount > 1)
2386 return setError(VBOX_E_INVALID_OBJECT_STATE,
2387 tr("Snapshot '%s' of the machine '%s' cannot be deleted, because it has %d child snapshots, which is more than the one snapshot allowed for deletion"),
2388 pSnapshot->i_getName().c_str(),
2389 mUserData->s.strName.c_str(),
2390 childrenCount);
2391
2392 if (pSnapshot == mData->mCurrentSnapshot && childrenCount >= 1)
2393 return setError(VBOX_E_INVALID_OBJECT_STATE,
2394 tr("Snapshot '%s' of the machine '%s' cannot be deleted, because it is the current snapshot and has one child snapshot"),
2395 pSnapshot->i_getName().c_str(),
2396 mUserData->s.strName.c_str());
2397
2398 /* If the snapshot being deleted is the current one, ensure current
2399 * settings are committed and saved.
2400 */
2401 if (pSnapshot == mData->mCurrentSnapshot)
2402 {
2403 if (mData->flModifications)
2404 {
2405 rc = i_saveSettings(NULL);
2406 // no need to change for whether VirtualBox.xml needs saving since
2407 // we can't have a machine XML rename pending at this point
2408 if (FAILED(rc)) return rc;
2409 }
2410 }
2411
2412 ComObjPtr<SnapshotMachine> pSnapMachine = pSnapshot->i_getSnapshotMachine();
2413
2414 /* create a progress object. The number of operations is:
2415 * 1 (preparing) + 1 if the snapshot is online + # of normal hard disks
2416 */
2417 LogFlowThisFunc(("Going thru snapshot machine attachments to determine progress setup\n"));
2418
2419 ULONG ulOpCount = 1; // one for preparations
2420 ULONG ulTotalWeight = 1; // one for preparations
2421
2422 if (pSnapshot->i_getStateFilePath().length())
2423 {
2424 ++ulOpCount;
2425 ++ulTotalWeight; // assume 1 MB for deleting the state file
2426 }
2427
2428 // count normal hard disks and add their sizes to the weight
2429 for (MediaData::AttachmentList::iterator it = pSnapMachine->mMediaData->mAttachments.begin();
2430 it != pSnapMachine->mMediaData->mAttachments.end();
2431 ++it)
2432 {
2433 ComObjPtr<MediumAttachment> &pAttach = *it;
2434 AutoReadLock attachLock(pAttach COMMA_LOCKVAL_SRC_POS);
2435 if (pAttach->i_getType() == DeviceType_HardDisk)
2436 {
2437 ComObjPtr<Medium> pHD = pAttach->i_getMedium();
2438 Assert(pHD);
2439 AutoReadLock mlock(pHD COMMA_LOCKVAL_SRC_POS);
2440
2441 MediumType_T type = pHD->i_getType();
2442 // writethrough and shareable images are unaffected by snapshots,
2443 // so do nothing for them
2444 if ( type != MediumType_Writethrough
2445 && type != MediumType_Shareable
2446 && type != MediumType_Readonly)
2447 {
2448 // normal or immutable media need attention
2449 ++ulOpCount;
2450 ulTotalWeight += (ULONG)(pHD->i_getSize() / _1M);
2451 }
2452 LogFlowThisFunc(("op %d: considering hard disk attachment %s\n", ulOpCount, pHD->i_getName().c_str()));
2453 }
2454 }
2455
2456 ComObjPtr<Progress> pProgress;
2457 pProgress.createObject();
2458 pProgress->init(mParent, static_cast<IMachine*>(this),
2459 BstrFmt(tr("Deleting snapshot '%s'"), pSnapshot->i_getName().c_str()).raw(),
2460 FALSE /* aCancelable */,
2461 ulOpCount,
2462 ulTotalWeight,
2463 Bstr(tr("Setting up")).raw(),
2464 1);
2465
2466 bool fDeleteOnline = ( (mData->mMachineState == MachineState_Running)
2467 || (mData->mMachineState == MachineState_Paused));
2468
2469 /* create and start the task on a separate thread */
2470 DeleteSnapshotTask *pTask = new DeleteSnapshotTask(this, pProgress,
2471 "DeleteSnap",
2472 fDeleteOnline,
2473 pSnapshot);
2474 rc = pTask->createThread();
2475 if (FAILED(rc))
2476 return rc;
2477
2478 // the task might start running but will block on acquiring the machine's write lock
2479 // which we acquired above; once this function leaves, the task will be unblocked;
2480 // set the proper machine state here now (note: after creating a Task instance)
2481 if (mData->mMachineState == MachineState_Running)
2482 i_setMachineState(MachineState_DeletingSnapshotOnline);
2483 else if (mData->mMachineState == MachineState_Paused)
2484 i_setMachineState(MachineState_DeletingSnapshotPaused);
2485 else
2486 i_setMachineState(MachineState_DeletingSnapshot);
2487 i_updateMachineStateOnClient();
2488
2489 /* return the progress to the caller */
2490 pProgress.queryInterfaceTo(aProgress.asOutParam());
2491
2492 LogFlowThisFuncLeave();
2493
2494 return S_OK;
2495}
2496
2497/**
2498 * Helper struct for SessionMachine::deleteSnapshotHandler().
2499 */
2500struct MediumDeleteRec
2501{
2502 MediumDeleteRec()
2503 : mfNeedsOnlineMerge(false),
2504 mpMediumLockList(NULL)
2505 {}
2506
2507 MediumDeleteRec(const ComObjPtr<Medium> &aHd,
2508 const ComObjPtr<Medium> &aSource,
2509 const ComObjPtr<Medium> &aTarget,
2510 const ComObjPtr<MediumAttachment> &aOnlineMediumAttachment,
2511 bool fMergeForward,
2512 const ComObjPtr<Medium> &aParentForTarget,
2513 MediumLockList *aChildrenToReparent,
2514 bool fNeedsOnlineMerge,
2515 MediumLockList *aMediumLockList,
2516 const ComPtr<IToken> &aHDLockToken)
2517 : mpHD(aHd),
2518 mpSource(aSource),
2519 mpTarget(aTarget),
2520 mpOnlineMediumAttachment(aOnlineMediumAttachment),
2521 mfMergeForward(fMergeForward),
2522 mpParentForTarget(aParentForTarget),
2523 mpChildrenToReparent(aChildrenToReparent),
2524 mfNeedsOnlineMerge(fNeedsOnlineMerge),
2525 mpMediumLockList(aMediumLockList),
2526 mpHDLockToken(aHDLockToken)
2527 {}
2528
2529 MediumDeleteRec(const ComObjPtr<Medium> &aHd,
2530 const ComObjPtr<Medium> &aSource,
2531 const ComObjPtr<Medium> &aTarget,
2532 const ComObjPtr<MediumAttachment> &aOnlineMediumAttachment,
2533 bool fMergeForward,
2534 const ComObjPtr<Medium> &aParentForTarget,
2535 MediumLockList *aChildrenToReparent,
2536 bool fNeedsOnlineMerge,
2537 MediumLockList *aMediumLockList,
2538 const ComPtr<IToken> &aHDLockToken,
2539 const Guid &aMachineId,
2540 const Guid &aSnapshotId)
2541 : mpHD(aHd),
2542 mpSource(aSource),
2543 mpTarget(aTarget),
2544 mpOnlineMediumAttachment(aOnlineMediumAttachment),
2545 mfMergeForward(fMergeForward),
2546 mpParentForTarget(aParentForTarget),
2547 mpChildrenToReparent(aChildrenToReparent),
2548 mfNeedsOnlineMerge(fNeedsOnlineMerge),
2549 mpMediumLockList(aMediumLockList),
2550 mpHDLockToken(aHDLockToken),
2551 mMachineId(aMachineId),
2552 mSnapshotId(aSnapshotId)
2553 {}
2554
2555 ComObjPtr<Medium> mpHD;
2556 ComObjPtr<Medium> mpSource;
2557 ComObjPtr<Medium> mpTarget;
2558 ComObjPtr<MediumAttachment> mpOnlineMediumAttachment;
2559 bool mfMergeForward;
2560 ComObjPtr<Medium> mpParentForTarget;
2561 MediumLockList *mpChildrenToReparent;
2562 bool mfNeedsOnlineMerge;
2563 MediumLockList *mpMediumLockList;
2564 /** optional lock token, used only in case mpHD is not merged/deleted */
2565 ComPtr<IToken> mpHDLockToken;
2566 /* these are for reattaching the hard disk in case of a failure: */
2567 Guid mMachineId;
2568 Guid mSnapshotId;
2569};
2570
2571typedef std::list<MediumDeleteRec> MediumDeleteRecList;
2572
2573/**
2574 * Worker method for the delete snapshot thread created by
2575 * SessionMachine::DeleteSnapshot(). This method gets called indirectly
2576 * through SessionMachine::taskHandler() which then calls
2577 * DeleteSnapshotTask::handler().
2578 *
2579 * The DeleteSnapshotTask contains the progress object returned to the console
2580 * by SessionMachine::DeleteSnapshot, through which progress and results are
2581 * reported.
2582 *
2583 * SessionMachine::DeleteSnapshot() has set the machine state to
2584 * MachineState_DeletingSnapshot right after creating this task. Since we block
2585 * on the machine write lock at the beginning, once that has been acquired, we
2586 * can assume that the machine state is indeed that.
2587 *
2588 * @note Locks the machine + the snapshot + the media tree for writing!
2589 *
2590 * @param pTask Task data.
2591 */
2592void SessionMachine::i_deleteSnapshotHandler(DeleteSnapshotTask &task)
2593{
2594 LogFlowThisFuncEnter();
2595
2596 HRESULT rc = S_OK;
2597 AutoCaller autoCaller(this);
2598 LogFlowThisFunc(("state=%d\n", getObjectState().getState()));
2599 if (FAILED(autoCaller.rc()))
2600 {
2601 /* we might have been uninitialized because the session was accidentally
2602 * closed by the client, so don't assert */
2603 rc = setError(E_FAIL,
2604 tr("The session has been accidentally closed"));
2605 task.m_pProgress->i_notifyComplete(rc);
2606 LogFlowThisFuncLeave();
2607 return;
2608 }
2609
2610 MediumDeleteRecList toDelete;
2611 Guid snapshotId;
2612
2613 try
2614 {
2615 /* Locking order: */
2616 AutoMultiWriteLock2 multiLock(this->lockHandle(), // machine
2617 task.m_pSnapshot->lockHandle() // snapshot
2618 COMMA_LOCKVAL_SRC_POS);
2619 // once we have this lock, we know that SessionMachine::DeleteSnapshot()
2620 // has exited after setting the machine state to MachineState_DeletingSnapshot
2621
2622 AutoWriteLock treeLock(mParent->i_getMediaTreeLockHandle()
2623 COMMA_LOCKVAL_SRC_POS);
2624
2625 ComObjPtr<SnapshotMachine> pSnapMachine = task.m_pSnapshot->i_getSnapshotMachine();
2626 // no need to lock the snapshot machine since it is const by definition
2627 Guid machineId = pSnapMachine->i_getId();
2628
2629 // save the snapshot ID (for callbacks)
2630 snapshotId = task.m_pSnapshot->i_getId();
2631
2632 // first pass:
2633 LogFlowThisFunc(("1: Checking hard disk merge prerequisites...\n"));
2634
2635 // Go thru the attachments of the snapshot machine (the media in here
2636 // point to the disk states _before_ the snapshot was taken, i.e. the
2637 // state we're restoring to; for each such medium, we will need to
2638 // merge it with its one and only child (the diff image holding the
2639 // changes written after the snapshot was taken).
2640 for (MediaData::AttachmentList::iterator it = pSnapMachine->mMediaData->mAttachments.begin();
2641 it != pSnapMachine->mMediaData->mAttachments.end();
2642 ++it)
2643 {
2644 ComObjPtr<MediumAttachment> &pAttach = *it;
2645 AutoReadLock attachLock(pAttach COMMA_LOCKVAL_SRC_POS);
2646 if (pAttach->i_getType() != DeviceType_HardDisk)
2647 continue;
2648
2649 ComObjPtr<Medium> pHD = pAttach->i_getMedium();
2650 Assert(!pHD.isNull());
2651
2652 {
2653 // writethrough, shareable and readonly images are
2654 // unaffected by snapshots, skip them
2655 AutoReadLock medlock(pHD COMMA_LOCKVAL_SRC_POS);
2656 MediumType_T type = pHD->i_getType();
2657 if ( type == MediumType_Writethrough
2658 || type == MediumType_Shareable
2659 || type == MediumType_Readonly)
2660 continue;
2661 }
2662
2663#ifdef DEBUG
2664 pHD->i_dumpBackRefs();
2665#endif
2666
2667 // needs to be merged with child or deleted, check prerequisites
2668 ComObjPtr<Medium> pTarget;
2669 ComObjPtr<Medium> pSource;
2670 bool fMergeForward = false;
2671 ComObjPtr<Medium> pParentForTarget;
2672 MediumLockList *pChildrenToReparent = NULL;
2673 bool fNeedsOnlineMerge = false;
2674 bool fOnlineMergePossible = task.m_fDeleteOnline;
2675 MediumLockList *pMediumLockList = NULL;
2676 MediumLockList *pVMMALockList = NULL;
2677 ComPtr<IToken> pHDLockToken;
2678 ComObjPtr<MediumAttachment> pOnlineMediumAttachment;
2679 if (fOnlineMergePossible)
2680 {
2681 // Look up the corresponding medium attachment in the currently
2682 // running VM. Any failure prevents a live merge. Could be made
2683 // a tad smarter by trying a few candidates, so that e.g. disks
2684 // which are simply moved to a different controller slot do not
2685 // prevent online merging in general.
2686 pOnlineMediumAttachment =
2687 i_findAttachment(mMediaData->mAttachments,
2688 pAttach->i_getControllerName().raw(),
2689 pAttach->i_getPort(),
2690 pAttach->i_getDevice());
2691 if (pOnlineMediumAttachment)
2692 {
2693 rc = mData->mSession.mLockedMedia.Get(pOnlineMediumAttachment,
2694 pVMMALockList);
2695 if (FAILED(rc))
2696 fOnlineMergePossible = false;
2697 }
2698 else
2699 fOnlineMergePossible = false;
2700 }
2701
2702 // no need to hold the lock any longer
2703 attachLock.release();
2704
2705 treeLock.release();
2706 rc = i_prepareDeleteSnapshotMedium(pHD, machineId, snapshotId,
2707 fOnlineMergePossible,
2708 pVMMALockList, pSource, pTarget,
2709 fMergeForward, pParentForTarget,
2710 pChildrenToReparent,
2711 fNeedsOnlineMerge,
2712 pMediumLockList,
2713 pHDLockToken);
2714 treeLock.acquire();
2715 if (FAILED(rc))
2716 throw rc;
2717
2718 // For simplicity, prepareDeleteSnapshotMedium selects the merge
2719 // direction in the following way: we merge pHD onto its child
2720 // (forward merge), not the other way round, because that saves us
2721 // from unnecessarily shuffling around the attachments for the
2722 // machine that follows the snapshot (next snapshot or current
2723 // state), unless it's a base image. Backwards merges of the first
2724 // snapshot into the base image is essential, as it ensures that
2725 // when all snapshots are deleted the only remaining image is a
2726 // base image. Important e.g. for medium formats which do not have
2727 // a file representation such as iSCSI.
2728
2729 // a couple paranoia checks for backward merges
2730 if (pMediumLockList != NULL && !fMergeForward)
2731 {
2732 // parent is null -> this disk is a base hard disk: we will
2733 // then do a backward merge, i.e. merge its only child onto the
2734 // base disk. Here we need then to update the attachment that
2735 // refers to the child and have it point to the parent instead
2736 Assert(pHD->i_getChildren().size() == 1);
2737
2738 ComObjPtr<Medium> pReplaceHD = pHD->i_getChildren().front();
2739
2740 ComAssertThrow(pReplaceHD == pSource, E_FAIL);
2741 }
2742
2743 Guid replaceMachineId;
2744 Guid replaceSnapshotId;
2745
2746 const Guid *pReplaceMachineId = pSource->i_getFirstMachineBackrefId();
2747 // minimal sanity checking
2748 Assert(!pReplaceMachineId || *pReplaceMachineId == mData->mUuid);
2749 if (pReplaceMachineId)
2750 replaceMachineId = *pReplaceMachineId;
2751
2752 const Guid *pSnapshotId = pSource->i_getFirstMachineBackrefSnapshotId();
2753 if (pSnapshotId)
2754 replaceSnapshotId = *pSnapshotId;
2755
2756 if (replaceMachineId.isValid() && !replaceMachineId.isZero())
2757 {
2758 // Adjust the backreferences, otherwise merging will assert.
2759 // Note that the medium attachment object stays associated
2760 // with the snapshot until the merge was successful.
2761 HRESULT rc2 = S_OK;
2762 rc2 = pSource->i_removeBackReference(replaceMachineId, replaceSnapshotId);
2763 AssertComRC(rc2);
2764
2765 toDelete.push_back(MediumDeleteRec(pHD, pSource, pTarget,
2766 pOnlineMediumAttachment,
2767 fMergeForward,
2768 pParentForTarget,
2769 pChildrenToReparent,
2770 fNeedsOnlineMerge,
2771 pMediumLockList,
2772 pHDLockToken,
2773 replaceMachineId,
2774 replaceSnapshotId));
2775 }
2776 else
2777 toDelete.push_back(MediumDeleteRec(pHD, pSource, pTarget,
2778 pOnlineMediumAttachment,
2779 fMergeForward,
2780 pParentForTarget,
2781 pChildrenToReparent,
2782 fNeedsOnlineMerge,
2783 pMediumLockList,
2784 pHDLockToken));
2785 }
2786
2787 {
2788 /*check available place on the storage*/
2789 RTFOFF pcbTotal = 0;
2790 RTFOFF pcbFree = 0;
2791 uint32_t pcbBlock = 0;
2792 uint32_t pcbSector = 0;
2793 std::multimap<uint32_t,uint64_t> neededStorageFreeSpace;
2794 std::map<uint32_t,const char*> serialMapToStoragePath;
2795
2796 MediumDeleteRecList::const_iterator it_md = toDelete.begin();
2797
2798 while (it_md != toDelete.end())
2799 {
2800 uint64_t diskSize = 0;
2801 uint32_t pu32Serial = 0;
2802 ComObjPtr<Medium> pSource_local = it_md->mpSource;
2803 ComObjPtr<Medium> pTarget_local = it_md->mpTarget;
2804 ComPtr<IMediumFormat> pTargetFormat;
2805
2806 {
2807 if ( pSource_local.isNull()
2808 || pSource_local == pTarget_local)
2809 {
2810 ++it_md;
2811 continue;
2812 }
2813 }
2814
2815 rc = pTarget_local->COMGETTER(MediumFormat)(pTargetFormat.asOutParam());
2816 if (FAILED(rc))
2817 throw rc;
2818
2819 if(pTarget_local->i_isMediumFormatFile())
2820 {
2821 int vrc = RTFsQuerySerial(pTarget_local->i_getLocationFull().c_str(), &pu32Serial);
2822 if (RT_FAILURE(vrc))
2823 {
2824 rc = setError(E_FAIL,
2825 tr(" Unable to merge storage '%s'. Can't get storage UID "),
2826 pTarget_local->i_getLocationFull().c_str());
2827 throw rc;
2828 }
2829
2830 pSource_local->COMGETTER(Size)((LONG64*)&diskSize);
2831
2832 /* store needed free space in multimap */
2833 neededStorageFreeSpace.insert(std::make_pair(pu32Serial,diskSize));
2834 /* linking storage UID with snapshot path, it is a helper container (just for easy finding needed path) */
2835 serialMapToStoragePath.insert(std::make_pair(pu32Serial,pTarget_local->i_getLocationFull().c_str()));
2836 }
2837
2838 ++it_md;
2839 }
2840
2841 while (!neededStorageFreeSpace.empty())
2842 {
2843 std::pair<std::multimap<uint32_t,uint64_t>::iterator,std::multimap<uint32_t,uint64_t>::iterator> ret;
2844 uint64_t commonSourceStoragesSize = 0;
2845
2846 /* find all records in multimap with identical storage UID*/
2847 ret = neededStorageFreeSpace.equal_range(neededStorageFreeSpace.begin()->first);
2848 std::multimap<uint32_t,uint64_t>::const_iterator it_ns = ret.first;
2849
2850 for (; it_ns != ret.second ; ++it_ns)
2851 {
2852 commonSourceStoragesSize += it_ns->second;
2853 }
2854
2855 /* find appropriate path by storage UID*/
2856 std::map<uint32_t,const char*>::const_iterator it_sm = serialMapToStoragePath.find(ret.first->first);
2857 /* get info about a storage */
2858 if (it_sm == serialMapToStoragePath.end())
2859 {
2860 LogFlowThisFunc((" Path to the storage wasn't found...\n "));
2861
2862 rc = setError(E_INVALIDARG,
2863 tr(" Unable to merge storage '%s'. Path to the storage wasn't found. "),
2864 it_sm->second);
2865 throw rc;
2866 }
2867
2868 int vrc = RTFsQuerySizes(it_sm->second, &pcbTotal, &pcbFree,&pcbBlock, &pcbSector);
2869 if (RT_FAILURE(vrc))
2870 {
2871 rc = setError(E_FAIL,
2872 tr(" Unable to merge storage '%s'. Can't get the storage size. "),
2873 it_sm->second);
2874 throw rc;
2875 }
2876
2877 if (commonSourceStoragesSize > (uint64_t)pcbFree)
2878 {
2879 LogFlowThisFunc((" Not enough free space to merge...\n "));
2880
2881 rc = setError(E_OUTOFMEMORY,
2882 tr(" Unable to merge storage '%s' - not enough free storage space. "),
2883 it_sm->second);
2884 throw rc;
2885 }
2886
2887 neededStorageFreeSpace.erase(ret.first, ret.second);
2888 }
2889
2890 serialMapToStoragePath.clear();
2891 }
2892
2893 // we can release the locks now since the machine state is MachineState_DeletingSnapshot
2894 treeLock.release();
2895 multiLock.release();
2896
2897 /* Now we checked that we can successfully merge all normal hard disks
2898 * (unless a runtime error like end-of-disc happens). Now get rid of
2899 * the saved state (if present), as that will free some disk space.
2900 * The snapshot itself will be deleted as late as possible, so that
2901 * the user can repeat the delete operation if he runs out of disk
2902 * space or cancels the delete operation. */
2903
2904 /* second pass: */
2905 LogFlowThisFunc(("2: Deleting saved state...\n"));
2906
2907 {
2908 // saveAllSnapshots() needs a machine lock, and the snapshots
2909 // tree is protected by the machine lock as well
2910 AutoWriteLock machineLock(this COMMA_LOCKVAL_SRC_POS);
2911
2912 Utf8Str stateFilePath = task.m_pSnapshot->i_getStateFilePath();
2913 if (!stateFilePath.isEmpty())
2914 {
2915 task.m_pProgress->SetNextOperation(Bstr(tr("Deleting the execution state")).raw(),
2916 1); // weight
2917
2918 i_releaseSavedStateFile(stateFilePath, task.m_pSnapshot /* pSnapshotToIgnore */);
2919
2920 // machine will need saving now
2921 machineLock.release();
2922 mParent->i_markRegistryModified(i_getId());
2923 }
2924 }
2925
2926 /* third pass: */
2927 LogFlowThisFunc(("3: Performing actual hard disk merging...\n"));
2928
2929 /// @todo NEWMEDIA turn the following errors into warnings because the
2930 /// snapshot itself has been already deleted (and interpret these
2931 /// warnings properly on the GUI side)
2932 for (MediumDeleteRecList::iterator it = toDelete.begin();
2933 it != toDelete.end();)
2934 {
2935 const ComObjPtr<Medium> &pMedium(it->mpHD);
2936 ULONG ulWeight;
2937
2938 {
2939 AutoReadLock alock(pMedium COMMA_LOCKVAL_SRC_POS);
2940 ulWeight = (ULONG)(pMedium->i_getSize() / _1M);
2941 }
2942
2943 task.m_pProgress->SetNextOperation(BstrFmt(tr("Merging differencing image '%s'"),
2944 pMedium->i_getName().c_str()).raw(),
2945 ulWeight);
2946
2947 bool fNeedSourceUninit = false;
2948 bool fReparentTarget = false;
2949 if (it->mpMediumLockList == NULL)
2950 {
2951 /* no real merge needed, just updating state and delete
2952 * diff files if necessary */
2953 AutoMultiWriteLock2 mLock(&mParent->i_getMediaTreeLockHandle(), pMedium->lockHandle() COMMA_LOCKVAL_SRC_POS);
2954
2955 Assert( !it->mfMergeForward
2956 || pMedium->i_getChildren().size() == 0);
2957
2958 /* Delete the differencing hard disk (has no children). Two
2959 * exceptions: if it's the last medium in the chain or if it's
2960 * a backward merge we don't want to handle due to complexity.
2961 * In both cases leave the image in place. If it's the first
2962 * exception the user can delete it later if he wants. */
2963 if (!pMedium->i_getParent().isNull())
2964 {
2965 Assert(pMedium->i_getState() == MediumState_Deleting);
2966 /* No need to hold the lock any longer. */
2967 mLock.release();
2968 rc = pMedium->i_deleteStorage(&task.m_pProgress,
2969 true /* aWait */);
2970 if (FAILED(rc))
2971 throw rc;
2972
2973 // need to uninit the deleted medium
2974 fNeedSourceUninit = true;
2975 }
2976 }
2977 else
2978 {
2979 bool fNeedsSave = false;
2980 if (it->mfNeedsOnlineMerge)
2981 {
2982 // Put the medium merge information (MediumDeleteRec) where
2983 // SessionMachine::FinishOnlineMergeMedium can get at it.
2984 // This callback will arrive while onlineMergeMedium is
2985 // still executing, and there can't be two tasks.
2986 /// @todo r=klaus this hack needs to go, and the logic needs to be "unconvoluted", putting SessionMachine in charge of coordinating the reconfig/resume.
2987 mConsoleTaskData.mDeleteSnapshotInfo = (void *)&(*it);
2988 // online medium merge, in the direction decided earlier
2989 rc = i_onlineMergeMedium(it->mpOnlineMediumAttachment,
2990 it->mpSource,
2991 it->mpTarget,
2992 it->mfMergeForward,
2993 it->mpParentForTarget,
2994 it->mpChildrenToReparent,
2995 it->mpMediumLockList,
2996 task.m_pProgress,
2997 &fNeedsSave);
2998 mConsoleTaskData.mDeleteSnapshotInfo = NULL;
2999 }
3000 else
3001 {
3002 // normal medium merge, in the direction decided earlier
3003 rc = it->mpSource->i_mergeTo(it->mpTarget,
3004 it->mfMergeForward,
3005 it->mpParentForTarget,
3006 it->mpChildrenToReparent,
3007 it->mpMediumLockList,
3008 &task.m_pProgress,
3009 true /* aWait */);
3010 }
3011
3012 // If the merge failed, we need to do our best to have a usable
3013 // VM configuration afterwards. The return code doesn't tell
3014 // whether the merge completed and so we have to check if the
3015 // source medium (diff images are always file based at the
3016 // moment) is still there or not. Be careful not to lose the
3017 // error code below, before the "Delayed failure exit".
3018 if (FAILED(rc))
3019 {
3020 AutoReadLock mlock(it->mpSource COMMA_LOCKVAL_SRC_POS);
3021 if (!it->mpSource->i_isMediumFormatFile())
3022 // Diff medium not backed by a file - cannot get status so
3023 // be pessimistic.
3024 throw rc;
3025 const Utf8Str &loc = it->mpSource->i_getLocationFull();
3026 // Source medium is still there, so merge failed early.
3027 if (RTFileExists(loc.c_str()))
3028 throw rc;
3029
3030 // Source medium is gone. Assume the merge succeeded and
3031 // thus it's safe to remove the attachment. We use the
3032 // "Delayed failure exit" below.
3033 }
3034
3035 // need to change the medium attachment for backward merges
3036 fReparentTarget = !it->mfMergeForward;
3037
3038 if (!it->mfNeedsOnlineMerge)
3039 {
3040 // need to uninit the medium deleted by the merge
3041 fNeedSourceUninit = true;
3042
3043 // delete the no longer needed medium lock list, which
3044 // implicitly handled the unlocking
3045 delete it->mpMediumLockList;
3046 it->mpMediumLockList = NULL;
3047 }
3048 }
3049
3050 // Now that the medium is successfully merged/deleted/whatever,
3051 // remove the medium attachment from the snapshot. For a backwards
3052 // merge the target attachment needs to be removed from the
3053 // snapshot, as the VM will take it over. For forward merges the
3054 // source medium attachment needs to be removed.
3055 ComObjPtr<MediumAttachment> pAtt;
3056 if (fReparentTarget)
3057 {
3058 pAtt = i_findAttachment(pSnapMachine->mMediaData->mAttachments,
3059 it->mpTarget);
3060 it->mpTarget->i_removeBackReference(machineId, snapshotId);
3061 }
3062 else
3063 pAtt = i_findAttachment(pSnapMachine->mMediaData->mAttachments,
3064 it->mpSource);
3065 pSnapMachine->mMediaData->mAttachments.remove(pAtt);
3066
3067 if (fReparentTarget)
3068 {
3069 // Search for old source attachment and replace with target.
3070 // There can be only one child snapshot in this case.
3071 ComObjPtr<Machine> pMachine = this;
3072 Guid childSnapshotId;
3073 ComObjPtr<Snapshot> pChildSnapshot = task.m_pSnapshot->i_getFirstChild();
3074 if (pChildSnapshot)
3075 {
3076 pMachine = pChildSnapshot->i_getSnapshotMachine();
3077 childSnapshotId = pChildSnapshot->i_getId();
3078 }
3079 pAtt = i_findAttachment(pMachine->mMediaData->mAttachments, it->mpSource);
3080 if (pAtt)
3081 {
3082 AutoWriteLock attLock(pAtt COMMA_LOCKVAL_SRC_POS);
3083 pAtt->i_updateMedium(it->mpTarget);
3084 it->mpTarget->i_addBackReference(pMachine->mData->mUuid, childSnapshotId);
3085 }
3086 else
3087 {
3088 // If no attachment is found do not change anything. Maybe
3089 // the source medium was not attached to the snapshot.
3090 // If this is an online deletion the attachment was updated
3091 // already to allow the VM continue execution immediately.
3092 // Needs a bit of special treatment due to this difference.
3093 if (it->mfNeedsOnlineMerge)
3094 it->mpTarget->i_addBackReference(pMachine->mData->mUuid, childSnapshotId);
3095 }
3096 }
3097
3098 if (fNeedSourceUninit)
3099 it->mpSource->uninit();
3100
3101 // One attachment is merged, must save the settings
3102 mParent->i_markRegistryModified(i_getId());
3103
3104 // prevent calling cancelDeleteSnapshotMedium() for this attachment
3105 it = toDelete.erase(it);
3106
3107 // Delayed failure exit when the merge cleanup failed but the
3108 // merge actually succeeded.
3109 if (FAILED(rc))
3110 throw rc;
3111 }
3112
3113 {
3114 // beginSnapshotDelete() needs the machine lock, and the snapshots
3115 // tree is protected by the machine lock as well
3116 AutoWriteLock machineLock(this COMMA_LOCKVAL_SRC_POS);
3117
3118 task.m_pSnapshot->i_beginSnapshotDelete();
3119 task.m_pSnapshot->uninit();
3120
3121 machineLock.release();
3122 mParent->i_markRegistryModified(i_getId());
3123 }
3124 }
3125 catch (HRESULT aRC) {
3126 rc = aRC;
3127 }
3128
3129 if (FAILED(rc))
3130 {
3131 // preserve existing error info so that the result can
3132 // be properly reported to the progress object below
3133 ErrorInfoKeeper eik;
3134
3135 AutoMultiWriteLock2 multiLock(this->lockHandle(), // machine
3136 &mParent->i_getMediaTreeLockHandle() // media tree
3137 COMMA_LOCKVAL_SRC_POS);
3138
3139 // un-prepare the remaining hard disks
3140 for (MediumDeleteRecList::const_iterator it = toDelete.begin();
3141 it != toDelete.end();
3142 ++it)
3143 i_cancelDeleteSnapshotMedium(it->mpHD, it->mpSource,
3144 it->mpChildrenToReparent,
3145 it->mfNeedsOnlineMerge,
3146 it->mpMediumLockList, it->mpHDLockToken,
3147 it->mMachineId, it->mSnapshotId);
3148 }
3149
3150 // whether we were successful or not, we need to set the machine
3151 // state and save the machine settings;
3152 {
3153 // preserve existing error info so that the result can
3154 // be properly reported to the progress object below
3155 ErrorInfoKeeper eik;
3156
3157 // restore the machine state that was saved when the
3158 // task was started
3159 i_setMachineState(task.m_machineStateBackup);
3160 i_updateMachineStateOnClient();
3161
3162 mParent->i_saveModifiedRegistries();
3163 }
3164
3165 // report the result (this will try to fetch current error info on failure)
3166 task.m_pProgress->i_notifyComplete(rc);
3167
3168 if (SUCCEEDED(rc))
3169 mParent->i_onSnapshotDeleted(mData->mUuid, snapshotId);
3170
3171 LogFlowThisFunc(("Done deleting snapshot (rc=%08X)\n", rc));
3172 LogFlowThisFuncLeave();
3173}
3174
3175/**
3176 * Checks that this hard disk (part of a snapshot) may be deleted/merged and
3177 * performs necessary state changes. Must not be called for writethrough disks
3178 * because there is nothing to delete/merge then.
3179 *
3180 * This method is to be called prior to calling #deleteSnapshotMedium().
3181 * If #deleteSnapshotMedium() is not called or fails, the state modifications
3182 * performed by this method must be undone by #cancelDeleteSnapshotMedium().
3183 *
3184 * @return COM status code
3185 * @param aHD Hard disk which is connected to the snapshot.
3186 * @param aMachineId UUID of machine this hard disk is attached to.
3187 * @param aSnapshotId UUID of snapshot this hard disk is attached to. May
3188 * be a zero UUID if no snapshot is applicable.
3189 * @param fOnlineMergePossible Flag whether an online merge is possible.
3190 * @param aVMMALockList Medium lock list for the medium attachment of this VM.
3191 * Only used if @a fOnlineMergePossible is @c true, and
3192 * must be non-NULL in this case.
3193 * @param aSource Source hard disk for merge (out).
3194 * @param aTarget Target hard disk for merge (out).
3195 * @param aMergeForward Merge direction decision (out).
3196 * @param aParentForTarget New parent if target needs to be reparented (out).
3197 * @param aChildrenToReparent MediumLockList with children which have to be
3198 * reparented to the target (out).
3199 * @param fNeedsOnlineMerge Whether this merge needs to be done online (out).
3200 * If this is set to @a true then the @a aVMMALockList
3201 * parameter has been modified and is returned as
3202 * @a aMediumLockList.
3203 * @param aMediumLockList Where to store the created medium lock list (may
3204 * return NULL if no real merge is necessary).
3205 * @param aHDLockToken Where to store the write lock token for aHD, in case
3206 * it is not merged or deleted (out).
3207 *
3208 * @note Caller must hold media tree lock for writing. This locks this object
3209 * and every medium object on the merge chain for writing.
3210 */
3211HRESULT SessionMachine::i_prepareDeleteSnapshotMedium(const ComObjPtr<Medium> &aHD,
3212 const Guid &aMachineId,
3213 const Guid &aSnapshotId,
3214 bool fOnlineMergePossible,
3215 MediumLockList *aVMMALockList,
3216 ComObjPtr<Medium> &aSource,
3217 ComObjPtr<Medium> &aTarget,
3218 bool &aMergeForward,
3219 ComObjPtr<Medium> &aParentForTarget,
3220 MediumLockList * &aChildrenToReparent,
3221 bool &fNeedsOnlineMerge,
3222 MediumLockList * &aMediumLockList,
3223 ComPtr<IToken> &aHDLockToken)
3224{
3225 Assert(!mParent->i_getMediaTreeLockHandle().isWriteLockOnCurrentThread());
3226 Assert(!fOnlineMergePossible || VALID_PTR(aVMMALockList));
3227
3228 AutoWriteLock alock(aHD COMMA_LOCKVAL_SRC_POS);
3229
3230 // Medium must not be writethrough/shareable/readonly at this point
3231 MediumType_T type = aHD->i_getType();
3232 AssertReturn( type != MediumType_Writethrough
3233 && type != MediumType_Shareable
3234 && type != MediumType_Readonly, E_FAIL);
3235
3236 aChildrenToReparent = NULL;
3237 aMediumLockList = NULL;
3238 fNeedsOnlineMerge = false;
3239
3240 if (aHD->i_getChildren().size() == 0)
3241 {
3242 /* This technically is no merge, set those values nevertheless.
3243 * Helps with updating the medium attachments. */
3244 aSource = aHD;
3245 aTarget = aHD;
3246
3247 /* special treatment of the last hard disk in the chain: */
3248 if (aHD->i_getParent().isNull())
3249 {
3250 /* lock only, to prevent any usage until the snapshot deletion
3251 * is completed */
3252 alock.release();
3253 return aHD->LockWrite(aHDLockToken.asOutParam());
3254 }
3255
3256 /* the differencing hard disk w/o children will be deleted, protect it
3257 * from attaching to other VMs (this is why Deleting) */
3258 return aHD->i_markForDeletion();
3259 }
3260
3261 /* not going multi-merge as it's too expensive */
3262 if (aHD->i_getChildren().size() > 1)
3263 return setError(E_FAIL,
3264 tr("Hard disk '%s' has more than one child hard disk (%d)"),
3265 aHD->i_getLocationFull().c_str(),
3266 aHD->i_getChildren().size());
3267
3268 ComObjPtr<Medium> pChild = aHD->i_getChildren().front();
3269
3270 AutoWriteLock childLock(pChild COMMA_LOCKVAL_SRC_POS);
3271
3272 /* the rest is a normal merge setup */
3273 if (aHD->i_getParent().isNull())
3274 {
3275 /* base hard disk, backward merge */
3276 const Guid *pMachineId1 = pChild->i_getFirstMachineBackrefId();
3277 const Guid *pMachineId2 = aHD->i_getFirstMachineBackrefId();
3278 if (pMachineId1 && pMachineId2 && *pMachineId1 != *pMachineId2)
3279 {
3280 /* backward merge is too tricky, we'll just detach on snapshot
3281 * deletion, so lock only, to prevent any usage */
3282 childLock.release();
3283 alock.release();
3284 return aHD->LockWrite(aHDLockToken.asOutParam());
3285 }
3286
3287 aSource = pChild;
3288 aTarget = aHD;
3289 }
3290 else
3291 {
3292 /* Determine best merge direction. */
3293 bool fMergeForward = true;
3294
3295 childLock.release();
3296 alock.release();
3297 HRESULT rc = aHD->i_queryPreferredMergeDirection(pChild, fMergeForward);
3298 alock.acquire();
3299 childLock.acquire();
3300
3301 if (FAILED(rc) && rc != E_FAIL)
3302 return rc;
3303
3304 if (fMergeForward)
3305 {
3306 aSource = aHD;
3307 aTarget = pChild;
3308 LogFlowThisFunc(("Forward merging selected\n"));
3309 }
3310 else
3311 {
3312 aSource = pChild;
3313 aTarget = aHD;
3314 LogFlowThisFunc(("Backward merging selected\n"));
3315 }
3316 }
3317
3318 HRESULT rc;
3319 childLock.release();
3320 alock.release();
3321 rc = aSource->i_prepareMergeTo(aTarget, &aMachineId, &aSnapshotId,
3322 !fOnlineMergePossible /* fLockMedia */,
3323 aMergeForward, aParentForTarget,
3324 aChildrenToReparent, aMediumLockList);
3325 alock.acquire();
3326 childLock.acquire();
3327 if (SUCCEEDED(rc) && fOnlineMergePossible)
3328 {
3329 /* Try to lock the newly constructed medium lock list. If it succeeds
3330 * this can be handled as an offline merge, i.e. without the need of
3331 * asking the VM to do the merging. Only continue with the online
3332 * merging preparation if applicable. */
3333 childLock.release();
3334 alock.release();
3335 rc = aMediumLockList->Lock();
3336 alock.acquire();
3337 childLock.acquire();
3338 if (FAILED(rc) && fOnlineMergePossible)
3339 {
3340 /* Locking failed, this cannot be done as an offline merge. Try to
3341 * combine the locking information into the lock list of the medium
3342 * attachment in the running VM. If that fails or locking the
3343 * resulting lock list fails then the merge cannot be done online.
3344 * It can be repeated by the user when the VM is shut down. */
3345 MediumLockList::Base::iterator lockListVMMABegin =
3346 aVMMALockList->GetBegin();
3347 MediumLockList::Base::iterator lockListVMMAEnd =
3348 aVMMALockList->GetEnd();
3349 MediumLockList::Base::iterator lockListBegin =
3350 aMediumLockList->GetBegin();
3351 MediumLockList::Base::iterator lockListEnd =
3352 aMediumLockList->GetEnd();
3353 for (MediumLockList::Base::iterator it = lockListVMMABegin,
3354 it2 = lockListBegin;
3355 it2 != lockListEnd;
3356 ++it, ++it2)
3357 {
3358 if ( it == lockListVMMAEnd
3359 || it->GetMedium() != it2->GetMedium())
3360 {
3361 fOnlineMergePossible = false;
3362 break;
3363 }
3364 bool fLockReq = (it2->GetLockRequest() || it->GetLockRequest());
3365 childLock.release();
3366 alock.release();
3367 rc = it->UpdateLock(fLockReq);
3368 alock.acquire();
3369 childLock.acquire();
3370 if (FAILED(rc))
3371 {
3372 // could not update the lock, trigger cleanup below
3373 fOnlineMergePossible = false;
3374 break;
3375 }
3376 }
3377
3378 if (fOnlineMergePossible)
3379 {
3380 /* we will lock the children of the source for reparenting */
3381 if (aChildrenToReparent && !aChildrenToReparent->IsEmpty())
3382 {
3383 /* Cannot just call aChildrenToReparent->Lock(), as one of
3384 * the children is the one under which the current state of
3385 * the VM is located, and this means it is already locked
3386 * (for reading). Note that no special unlocking is needed,
3387 * because cancelMergeTo will unlock everything locked in
3388 * its context (using the unlock on destruction), and both
3389 * cancelDeleteSnapshotMedium (in case something fails) and
3390 * FinishOnlineMergeMedium re-define the read/write lock
3391 * state of everything which the VM need, search for the
3392 * UpdateLock method calls. */
3393 childLock.release();
3394 alock.release();
3395 rc = aChildrenToReparent->Lock(true /* fSkipOverLockedMedia */);
3396 alock.acquire();
3397 childLock.acquire();
3398 MediumLockList::Base::iterator childrenToReparentBegin = aChildrenToReparent->GetBegin();
3399 MediumLockList::Base::iterator childrenToReparentEnd = aChildrenToReparent->GetEnd();
3400 for (MediumLockList::Base::iterator it = childrenToReparentBegin;
3401 it != childrenToReparentEnd;
3402 ++it)
3403 {
3404 ComObjPtr<Medium> pMedium = it->GetMedium();
3405 AutoReadLock mediumLock(pMedium COMMA_LOCKVAL_SRC_POS);
3406 if (!it->IsLocked())
3407 {
3408 mediumLock.release();
3409 childLock.release();
3410 alock.release();
3411 rc = aVMMALockList->Update(pMedium, true);
3412 alock.acquire();
3413 childLock.acquire();
3414 mediumLock.acquire();
3415 if (FAILED(rc))
3416 throw rc;
3417 }
3418 }
3419 }
3420 }
3421
3422 if (fOnlineMergePossible)
3423 {
3424 childLock.release();
3425 alock.release();
3426 rc = aVMMALockList->Lock();
3427 alock.acquire();
3428 childLock.acquire();
3429 if (FAILED(rc))
3430 {
3431 aSource->i_cancelMergeTo(aChildrenToReparent, aMediumLockList);
3432 rc = setError(rc,
3433 tr("Cannot lock hard disk '%s' for a live merge"),
3434 aHD->i_getLocationFull().c_str());
3435 }
3436 else
3437 {
3438 delete aMediumLockList;
3439 aMediumLockList = aVMMALockList;
3440 fNeedsOnlineMerge = true;
3441 }
3442 }
3443 else
3444 {
3445 aSource->i_cancelMergeTo(aChildrenToReparent, aMediumLockList);
3446 rc = setError(rc,
3447 tr("Failed to construct lock list for a live merge of hard disk '%s'"),
3448 aHD->i_getLocationFull().c_str());
3449 }
3450
3451 // fix the VM's lock list if anything failed
3452 if (FAILED(rc))
3453 {
3454 lockListVMMABegin = aVMMALockList->GetBegin();
3455 lockListVMMAEnd = aVMMALockList->GetEnd();
3456 MediumLockList::Base::iterator lockListLast = lockListVMMAEnd;
3457 lockListLast--;
3458 for (MediumLockList::Base::iterator it = lockListVMMABegin;
3459 it != lockListVMMAEnd;
3460 ++it)
3461 {
3462 childLock.release();
3463 alock.release();
3464 it->UpdateLock(it == lockListLast);
3465 alock.acquire();
3466 childLock.acquire();
3467 ComObjPtr<Medium> pMedium = it->GetMedium();
3468 AutoWriteLock mediumLock(pMedium COMMA_LOCKVAL_SRC_POS);
3469 // blindly apply this, only needed for medium objects which
3470 // would be deleted as part of the merge
3471 pMedium->i_unmarkLockedForDeletion();
3472 }
3473 }
3474
3475 }
3476 else
3477 {
3478 aSource->i_cancelMergeTo(aChildrenToReparent, aMediumLockList);
3479 rc = setError(rc,
3480 tr("Cannot lock hard disk '%s' for an offline merge"),
3481 aHD->i_getLocationFull().c_str());
3482 }
3483 }
3484
3485 return rc;
3486}
3487
3488/**
3489 * Cancels the deletion/merging of this hard disk (part of a snapshot). Undoes
3490 * what #prepareDeleteSnapshotMedium() did. Must be called if
3491 * #deleteSnapshotMedium() is not called or fails.
3492 *
3493 * @param aHD Hard disk which is connected to the snapshot.
3494 * @param aSource Source hard disk for merge.
3495 * @param aChildrenToReparent Children to unlock.
3496 * @param fNeedsOnlineMerge Whether this merge needs to be done online.
3497 * @param aMediumLockList Medium locks to cancel.
3498 * @param aHDLockToken Optional write lock token for aHD.
3499 * @param aMachineId Machine id to attach the medium to.
3500 * @param aSnapshotId Snapshot id to attach the medium to.
3501 *
3502 * @note Locks the medium tree and the hard disks in the chain for writing.
3503 */
3504void SessionMachine::i_cancelDeleteSnapshotMedium(const ComObjPtr<Medium> &aHD,
3505 const ComObjPtr<Medium> &aSource,
3506 MediumLockList *aChildrenToReparent,
3507 bool fNeedsOnlineMerge,
3508 MediumLockList *aMediumLockList,
3509 const ComPtr<IToken> &aHDLockToken,
3510 const Guid &aMachineId,
3511 const Guid &aSnapshotId)
3512{
3513 if (aMediumLockList == NULL)
3514 {
3515 AutoMultiWriteLock2 mLock(&mParent->i_getMediaTreeLockHandle(), aHD->lockHandle() COMMA_LOCKVAL_SRC_POS);
3516
3517 Assert(aHD->i_getChildren().size() == 0);
3518
3519 if (aHD->i_getParent().isNull())
3520 {
3521 Assert(!aHDLockToken.isNull());
3522 if (!aHDLockToken.isNull())
3523 {
3524 HRESULT rc = aHDLockToken->Abandon();
3525 AssertComRC(rc);
3526 }
3527 }
3528 else
3529 {
3530 HRESULT rc = aHD->i_unmarkForDeletion();
3531 AssertComRC(rc);
3532 }
3533 }
3534 else
3535 {
3536 if (fNeedsOnlineMerge)
3537 {
3538 // Online merge uses the medium lock list of the VM, so give
3539 // an empty list to cancelMergeTo so that it works as designed.
3540 aSource->i_cancelMergeTo(aChildrenToReparent, new MediumLockList());
3541
3542 // clean up the VM medium lock list ourselves
3543 MediumLockList::Base::iterator lockListBegin =
3544 aMediumLockList->GetBegin();
3545 MediumLockList::Base::iterator lockListEnd =
3546 aMediumLockList->GetEnd();
3547 MediumLockList::Base::iterator lockListLast = lockListEnd;
3548 lockListLast--;
3549 for (MediumLockList::Base::iterator it = lockListBegin;
3550 it != lockListEnd;
3551 ++it)
3552 {
3553 ComObjPtr<Medium> pMedium = it->GetMedium();
3554 AutoWriteLock mediumLock(pMedium COMMA_LOCKVAL_SRC_POS);
3555 if (pMedium->i_getState() == MediumState_Deleting)
3556 pMedium->i_unmarkForDeletion();
3557 else
3558 {
3559 // blindly apply this, only needed for medium objects which
3560 // would be deleted as part of the merge
3561 pMedium->i_unmarkLockedForDeletion();
3562 }
3563 mediumLock.release();
3564 it->UpdateLock(it == lockListLast);
3565 mediumLock.acquire();
3566 }
3567 }
3568 else
3569 {
3570 aSource->i_cancelMergeTo(aChildrenToReparent, aMediumLockList);
3571 }
3572 }
3573
3574 if (aMachineId.isValid() && !aMachineId.isZero())
3575 {
3576 // reattach the source media to the snapshot
3577 HRESULT rc = aSource->i_addBackReference(aMachineId, aSnapshotId);
3578 AssertComRC(rc);
3579 }
3580}
3581
3582/**
3583 * Perform an online merge of a hard disk, i.e. the equivalent of
3584 * Medium::mergeTo(), just for running VMs. If this fails you need to call
3585 * #cancelDeleteSnapshotMedium().
3586 *
3587 * @return COM status code
3588 * @param aMediumAttachment Identify where the disk is attached in the VM.
3589 * @param aSource Source hard disk for merge.
3590 * @param aTarget Target hard disk for merge.
3591 * @param aMergeForward Merge direction.
3592 * @param aParentForTarget New parent if target needs to be reparented.
3593 * @param aChildrenToReparent Medium lock list with children which have to be
3594 * reparented to the target.
3595 * @param aMediumLockList Where to store the created medium lock list (may
3596 * return NULL if no real merge is necessary).
3597 * @param aProgress Progress indicator.
3598 * @param pfNeedsMachineSaveSettings Whether the VM settings need to be saved (out).
3599 */
3600HRESULT SessionMachine::i_onlineMergeMedium(const ComObjPtr<MediumAttachment> &aMediumAttachment,
3601 const ComObjPtr<Medium> &aSource,
3602 const ComObjPtr<Medium> &aTarget,
3603 bool fMergeForward,
3604 const ComObjPtr<Medium> &aParentForTarget,
3605 MediumLockList *aChildrenToReparent,
3606 MediumLockList *aMediumLockList,
3607 ComObjPtr<Progress> &aProgress,
3608 bool *pfNeedsMachineSaveSettings)
3609{
3610 AssertReturn(aSource != NULL, E_FAIL);
3611 AssertReturn(aTarget != NULL, E_FAIL);
3612 AssertReturn(aSource != aTarget, E_FAIL);
3613 AssertReturn(aMediumLockList != NULL, E_FAIL);
3614 NOREF(fMergeForward);
3615 NOREF(aParentForTarget);
3616 NOREF(aChildrenToReparent);
3617
3618 HRESULT rc = S_OK;
3619
3620 try
3621 {
3622 // Similar code appears in Medium::taskMergeHandle, so
3623 // if you make any changes below check whether they are applicable
3624 // in that context as well.
3625
3626 unsigned uTargetIdx = (unsigned)-1;
3627 unsigned uSourceIdx = (unsigned)-1;
3628 /* Sanity check all hard disks in the chain. */
3629 MediumLockList::Base::iterator lockListBegin =
3630 aMediumLockList->GetBegin();
3631 MediumLockList::Base::iterator lockListEnd =
3632 aMediumLockList->GetEnd();
3633 unsigned i = 0;
3634 for (MediumLockList::Base::iterator it = lockListBegin;
3635 it != lockListEnd;
3636 ++it)
3637 {
3638 MediumLock &mediumLock = *it;
3639 const ComObjPtr<Medium> &pMedium = mediumLock.GetMedium();
3640
3641 if (pMedium == aSource)
3642 uSourceIdx = i;
3643 else if (pMedium == aTarget)
3644 uTargetIdx = i;
3645
3646 // In Medium::taskMergeHandler there is lots of consistency
3647 // checking which we cannot do here, as the state details are
3648 // impossible to get outside the Medium class. The locking should
3649 // have done the checks already.
3650
3651 i++;
3652 }
3653
3654 ComAssertThrow( uSourceIdx != (unsigned)-1
3655 && uTargetIdx != (unsigned)-1, E_FAIL);
3656
3657 ComPtr<IInternalSessionControl> directControl;
3658 {
3659 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3660
3661 if (mData->mSession.mState != SessionState_Locked)
3662 throw setError(VBOX_E_INVALID_VM_STATE,
3663 tr("Machine is not locked by a session (session state: %s)"),
3664 Global::stringifySessionState(mData->mSession.mState));
3665 directControl = mData->mSession.mDirectControl;
3666 }
3667
3668 // Must not hold any locks here, as this will call back to finish
3669 // updating the medium attachment, chain linking and state.
3670 rc = directControl->OnlineMergeMedium(aMediumAttachment,
3671 uSourceIdx, uTargetIdx,
3672 aProgress);
3673 if (FAILED(rc))
3674 throw rc;
3675 }
3676 catch (HRESULT aRC) { rc = aRC; }
3677
3678 // The callback mentioned above takes care of update the medium state
3679
3680 if (pfNeedsMachineSaveSettings)
3681 *pfNeedsMachineSaveSettings = true;
3682
3683 return rc;
3684}
3685
3686/**
3687 * Implementation for IInternalMachineControl::finishOnlineMergeMedium().
3688 *
3689 * Gets called after the successful completion of an online merge from
3690 * Console::onlineMergeMedium(), which gets invoked indirectly above in
3691 * the call to IInternalSessionControl::onlineMergeMedium.
3692 *
3693 * This updates the medium information and medium state so that the VM
3694 * can continue with the updated state of the medium chain.
3695 */
3696HRESULT SessionMachine::finishOnlineMergeMedium()
3697{
3698 HRESULT rc = S_OK;
3699 MediumDeleteRec *pDeleteRec = (MediumDeleteRec *)mConsoleTaskData.mDeleteSnapshotInfo;
3700 AssertReturn(pDeleteRec, E_FAIL);
3701 bool fSourceHasChildren = false;
3702
3703 // all hard disks but the target were successfully deleted by
3704 // the merge; reparent target if necessary and uninitialize media
3705
3706 AutoWriteLock treeLock(mParent->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
3707
3708 // Declare this here to make sure the object does not get uninitialized
3709 // before this method completes. Would normally happen as halfway through
3710 // we delete the last reference to the no longer existing medium object.
3711 ComObjPtr<Medium> targetChild;
3712
3713 if (pDeleteRec->mfMergeForward)
3714 {
3715 // first, unregister the target since it may become a base
3716 // hard disk which needs re-registration
3717 rc = mParent->i_unregisterMedium(pDeleteRec->mpTarget);
3718 AssertComRC(rc);
3719
3720 // then, reparent it and disconnect the deleted branch at
3721 // both ends (chain->parent() is source's parent)
3722 pDeleteRec->mpTarget->i_deparent();
3723 pDeleteRec->mpTarget->i_setParent(pDeleteRec->mpParentForTarget);
3724 if (pDeleteRec->mpParentForTarget)
3725 pDeleteRec->mpSource->i_deparent();
3726
3727 // then, register again
3728 rc = mParent->i_registerMedium(pDeleteRec->mpTarget, &pDeleteRec->mpTarget, treeLock);
3729 AssertComRC(rc);
3730 }
3731 else
3732 {
3733 Assert(pDeleteRec->mpTarget->i_getChildren().size() == 1);
3734 targetChild = pDeleteRec->mpTarget->i_getChildren().front();
3735
3736 // disconnect the deleted branch at the elder end
3737 targetChild->i_deparent();
3738
3739 // Update parent UUIDs of the source's children, reparent them and
3740 // disconnect the deleted branch at the younger end
3741 if (pDeleteRec->mpChildrenToReparent && !pDeleteRec->mpChildrenToReparent->IsEmpty())
3742 {
3743 fSourceHasChildren = true;
3744 // Fix the parent UUID of the images which needs to be moved to
3745 // underneath target. The running machine has the images opened,
3746 // but only for reading since the VM is paused. If anything fails
3747 // we must continue. The worst possible result is that the images
3748 // need manual fixing via VBoxManage to adjust the parent UUID.
3749 treeLock.release();
3750 pDeleteRec->mpTarget->i_fixParentUuidOfChildren(pDeleteRec->mpChildrenToReparent);
3751 // The childen are still write locked, unlock them now and don't
3752 // rely on the destructor doing it very late.
3753 pDeleteRec->mpChildrenToReparent->Unlock();
3754 treeLock.acquire();
3755
3756 // obey {parent,child} lock order
3757 AutoWriteLock sourceLock(pDeleteRec->mpSource COMMA_LOCKVAL_SRC_POS);
3758
3759 MediumLockList::Base::iterator childrenBegin = pDeleteRec->mpChildrenToReparent->GetBegin();
3760 MediumLockList::Base::iterator childrenEnd = pDeleteRec->mpChildrenToReparent->GetEnd();
3761 for (MediumLockList::Base::iterator it = childrenBegin;
3762 it != childrenEnd;
3763 ++it)
3764 {
3765 Medium *pMedium = it->GetMedium();
3766 AutoWriteLock childLock(pMedium COMMA_LOCKVAL_SRC_POS);
3767
3768 pMedium->i_deparent(); // removes pMedium from source
3769 pMedium->i_setParent(pDeleteRec->mpTarget);
3770 }
3771 }
3772 }
3773
3774 /* unregister and uninitialize all hard disks removed by the merge */
3775 MediumLockList *pMediumLockList = NULL;
3776 rc = mData->mSession.mLockedMedia.Get(pDeleteRec->mpOnlineMediumAttachment, pMediumLockList);
3777 const ComObjPtr<Medium> &pLast = pDeleteRec->mfMergeForward ? pDeleteRec->mpTarget : pDeleteRec->mpSource;
3778 AssertReturn(SUCCEEDED(rc) && pMediumLockList, E_FAIL);
3779 MediumLockList::Base::iterator lockListBegin =
3780 pMediumLockList->GetBegin();
3781 MediumLockList::Base::iterator lockListEnd =
3782 pMediumLockList->GetEnd();
3783 for (MediumLockList::Base::iterator it = lockListBegin;
3784 it != lockListEnd;
3785 )
3786 {
3787 MediumLock &mediumLock = *it;
3788 /* Create a real copy of the medium pointer, as the medium
3789 * lock deletion below would invalidate the referenced object. */
3790 const ComObjPtr<Medium> pMedium = mediumLock.GetMedium();
3791
3792 /* The target and all images not merged (readonly) are skipped */
3793 if ( pMedium == pDeleteRec->mpTarget
3794 || pMedium->i_getState() == MediumState_LockedRead)
3795 {
3796 ++it;
3797 }
3798 else
3799 {
3800 rc = mParent->i_unregisterMedium(pMedium);
3801 AssertComRC(rc);
3802
3803 /* now, uninitialize the deleted hard disk (note that
3804 * due to the Deleting state, uninit() will not touch
3805 * the parent-child relationship so we need to
3806 * uninitialize each disk individually) */
3807
3808 /* note that the operation initiator hard disk (which is
3809 * normally also the source hard disk) is a special case
3810 * -- there is one more caller added by Task to it which
3811 * we must release. Also, if we are in sync mode, the
3812 * caller may still hold an AutoCaller instance for it
3813 * and therefore we cannot uninit() it (it's therefore
3814 * the caller's responsibility) */
3815 if (pMedium == pDeleteRec->mpSource)
3816 {
3817 Assert(pDeleteRec->mpSource->i_getChildren().size() == 0);
3818 Assert(pDeleteRec->mpSource->i_getFirstMachineBackrefId() == NULL);
3819 }
3820
3821 /* Delete the medium lock list entry, which also releases the
3822 * caller added by MergeChain before uninit() and updates the
3823 * iterator to point to the right place. */
3824 rc = pMediumLockList->RemoveByIterator(it);
3825 AssertComRC(rc);
3826
3827 treeLock.release();
3828 pMedium->uninit();
3829 treeLock.acquire();
3830 }
3831
3832 /* Stop as soon as we reached the last medium affected by the merge.
3833 * The remaining images must be kept unchanged. */
3834 if (pMedium == pLast)
3835 break;
3836 }
3837
3838 /* Could be in principle folded into the previous loop, but let's keep
3839 * things simple. Update the medium locking to be the standard state:
3840 * all parent images locked for reading, just the last diff for writing. */
3841 lockListBegin = pMediumLockList->GetBegin();
3842 lockListEnd = pMediumLockList->GetEnd();
3843 MediumLockList::Base::iterator lockListLast = lockListEnd;
3844 lockListLast--;
3845 for (MediumLockList::Base::iterator it = lockListBegin;
3846 it != lockListEnd;
3847 ++it)
3848 {
3849 it->UpdateLock(it == lockListLast);
3850 }
3851
3852 /* If this is a backwards merge of the only remaining snapshot (i.e. the
3853 * source has no children) then update the medium associated with the
3854 * attachment, as the previously associated one (source) is now deleted.
3855 * Without the immediate update the VM could not continue running. */
3856 if (!pDeleteRec->mfMergeForward && !fSourceHasChildren)
3857 {
3858 AutoWriteLock attLock(pDeleteRec->mpOnlineMediumAttachment COMMA_LOCKVAL_SRC_POS);
3859 pDeleteRec->mpOnlineMediumAttachment->i_updateMedium(pDeleteRec->mpTarget);
3860 }
3861
3862 return S_OK;
3863}
3864
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