VirtualBox

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

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

Main/Medium+Snapshot: make all code recursing over trees (objects containing lists of child objects) use as little stack as possible, and establish safe depth limits to avoid crashes, plus related cleanups in related code areas

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