VirtualBox

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

Last change on this file since 94598 was 94598, checked in by vboxsync, 3 years ago

Main/Machine+Medium+Snapshot+VirtualBox: Recursion elimination to save stack space. Caused trouble with the current settings limits already in ASAN builds, now much higher limits would be possible, but that's not urgent. Also fix the behavior of forgetting medium objects when unregistering VMs (was previously not doing what the API documentation said in the CleanupMode_UnregisterOnly case). bugref:7717

Settings.cpp: Recursion elimination and make the handling of settings reading and writing more similar.

ValidationKit/tests/api/tdTreeDepth1.py: Improve testcase. Make sure that VM unregistering does the right thing with the associated medium objects.

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

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