VirtualBox

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

Last change on this file since 64212 was 64212, checked in by vboxsync, 9 years ago

Main/Snapshot: make sure that the source medium has no parent any more when we're certain that we no longer want to know about it (even if an error happened), as otherwise this leads to confusion and crashes later, because there would be an attempt to save it as part of a VM's medium registry when the medium isn't operational any more

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