VirtualBox

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

Last change on this file since 105659 was 105605, checked in by vboxsync, 4 months ago

Recording/Main: Renaming: Dropped the superfluous "Settings" suffix of the settings namespace recording classes. This makes those classes easier to find, also for editors which don't support namespace handling, as the same class names were used for the actual implementation classes (i.e. RecordingSettingsImpl.cpp -> RecordingSettings). No functional changes.

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

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