VirtualBox

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

最後變更 在這個檔案從81425是 81425,由 vboxsync 提交於 5 年 前

Main/Machine+BIOSSettings+Console: Full implementation of NVRAM handling (part of VM delete, rename, clone and move code in combination with taking, deleting and restoring snapshots). Corresponding console update (ripping out old, never really used NVRAM handling).

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

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