VirtualBox

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

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

Main/Snapshot: not able to restore from a snapshot more than once if VM
has multiple snapshots. bugref:10252

If a VM has multiple snapshots then restoring from any snapshot beyond
the first will result in that snapshot being deleted during power up.
Any attempt to restore from this snapshot in the future will thus fail
as the file is now gone. Fixed by updating Snapshot::i_sharesSavedStateFile()
to check each snapshot's saved state file rather than just the first
snapshot.

Also 'VBoxManage unregistervm x --delete' fails to remove snapshots due
to a regression in Snapshot::i_uninitAll() which accidentally inverted
the logic when testing if the snapshot was in the llFilenames list
already.

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