VirtualBox

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

Last change on this file since 107438 was 106832, checked in by vboxsync, 3 months ago

Main/{Machine,Medium,Snapshot}: Address broken reference counting of
medium back references when snapshots are involved which could cause
back references to get removed while the medium was still in use. Also
resolved some locking issues for various failure scenarios when creating
or deleting snapshots.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 163.6 KB
Line 
1/* $Id: SnapshotImpl.cpp 106832 2024-11-05 11:35:04Z vboxsync $ */
2/** @file
3 * COM class implementation for Snapshot and SnapshotMachine in VBoxSVC.
4 */
5
6/*
7 * Copyright (C) 2006-2024 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 /* We need to set fBeganTakingSnapshot=true here to handle not only the
1862 * scenario of i_createImplicitDiffs() succeeding but also the failure case
1863 * since differencing hard disks can still be created which then need to be
1864 * cleaned up and deleted via i_finishTakingSnapshot() below. */
1865 fBeganTakingSnapshot = true;
1866
1867 alock.release();
1868 /* create new differencing hard disks and attach them to this machine */
1869 hrc = i_createImplicitDiffs(task.m_pProgress,
1870 1, // operation weight; must be the same as in Machine::TakeSnapshot()
1871 task.m_fTakingSnapshotOnline);
1872 /* If i_createImplicitDiffs() fails the 'catch' block below calls
1873 * Snapshot::uninit() which requires the machine to be write locked
1874 * so reacquire alock here before testing for failure. */
1875 alock.acquire();
1876 if (FAILED(hrc))
1877 throw hrc;
1878
1879 // MUST NOT save the settings or the media registry here, because
1880 // this causes trouble with rolling back settings if the user cancels
1881 // taking the snapshot after the diff images have been created.
1882
1883 // STEP 3: save the VM state (if online)
1884 if (task.m_fTakingSnapshotOnline)
1885 {
1886 task.m_pProgress->SetNextOperation(Bstr(tr("Saving the machine state")).raw(),
1887 mHWData->mMemorySize); // operation weight, same as computed
1888 // when setting up progress object
1889
1890 if (task.m_strStateFilePath.isNotEmpty())
1891 {
1892 alock.release();
1893 task.m_pProgress->i_setCancelCallback(i_takeSnapshotProgressCancelCallback, &task);
1894 hrc = task.m_pDirectControl->SaveStateWithReason(Reason_Snapshot,
1895 task.m_pProgress,
1896 task.m_pSnapshot,
1897 Bstr(task.m_strStateFilePath).raw(),
1898 task.m_fPause,
1899 &fSuspendedBySave);
1900 task.m_pProgress->i_setCancelCallback(NULL, NULL);
1901 alock.acquire();
1902 if (FAILED(hrc))
1903 throw hrc;
1904 }
1905 else
1906 LogRel(("Machine: skipped saving state as part of online snapshot\n"));
1907
1908 if (FAILED(task.m_pProgress->NotifyPointOfNoReturn()))
1909 throw setError(E_FAIL, tr("Canceled"));
1910
1911 // STEP 4: reattach hard disks
1912 LogFlowThisFunc(("Reattaching new differencing hard disks...\n"));
1913
1914 task.m_pProgress->SetNextOperation(Bstr(tr("Reconfiguring medium attachments")).raw(),
1915 1); // operation weight, same as computed when setting up progress object
1916
1917 com::SafeIfaceArray<IMediumAttachment> atts;
1918 hrc = COMGETTER(MediumAttachments)(ComSafeArrayAsOutParam(atts));
1919 if (FAILED(hrc))
1920 throw hrc;
1921
1922 alock.release();
1923 hrc = task.m_pDirectControl->ReconfigureMediumAttachments(ComSafeArrayAsInParam(atts));
1924 alock.acquire();
1925 if (FAILED(hrc))
1926 throw hrc;
1927 }
1928
1929 // Handle NVRAM file snapshotting
1930 Utf8Str strNVRAM = mNvramStore->i_getNonVolatileStorageFile();
1931 Utf8Str strNVRAMSnap = pSnapshotMachine->i_getSnapshotNVRAMFilename();
1932 if ( strNVRAM.isNotEmpty()
1933 && strNVRAMSnap.isNotEmpty()
1934 && RTFileExists(strNVRAM.c_str())
1935 && mFirmwareSettings->i_getFirmwareType() != FirmwareType_BIOS)
1936 {
1937 Utf8Str strNVRAMSnapAbs;
1938 i_calculateFullPath(strNVRAMSnap, strNVRAMSnapAbs);
1939 hrc = VirtualBox::i_ensureFilePathExists(strNVRAMSnapAbs, true /* fCreate */);
1940 if (FAILED(hrc))
1941 throw hrc;
1942 int vrc = RTFileCopy(strNVRAM.c_str(), strNVRAMSnapAbs.c_str());
1943 if (RT_FAILURE(vrc))
1944 throw setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
1945 tr("Could not copy NVRAM file '%s' to '%s' (%Rrc)"),
1946 strNVRAM.c_str(), strNVRAMSnapAbs.c_str(), vrc);
1947 pSnapshotMachine->mNvramStore->i_updateNonVolatileStorageFile(strNVRAMSnap);
1948 }
1949
1950 // store parent of newly created diffs before commit for notify
1951 {
1952 MediumAttachmentList &oldAtts = *mMediumAttachments.backedUpData();
1953 for (MediumAttachmentList::const_iterator
1954 it = mMediumAttachments->begin();
1955 it != mMediumAttachments->end();
1956 ++it)
1957 {
1958 MediumAttachment *pAttach = *it;
1959 Medium *pMedium = pAttach->i_getMedium();
1960 if (!pMedium)
1961 continue;
1962
1963 bool fFound = false;
1964 /* was this medium attached before? */
1965 for (MediumAttachmentList::iterator
1966 oldIt = oldAtts.begin();
1967 oldIt != oldAtts.end();
1968 ++oldIt)
1969 {
1970 MediumAttachment *pOldAttach = *oldIt;
1971 if (pOldAttach->i_getMedium() == pMedium)
1972 {
1973 fFound = true;
1974 break;
1975 }
1976 }
1977 if (!fFound)
1978 {
1979 pMediaForNotify.insert(pMedium->i_getParent());
1980 uIdsForNotify[pMedium->i_getId()] = pMedium->i_getDeviceType();
1981 }
1982 }
1983 }
1984
1985 /*
1986 * Finalize the requested snapshot object. This will reset the
1987 * machine state to the state it had at the beginning.
1988 */
1989 hrc = i_finishTakingSnapshot(task, alock, true /*aSuccess*/); /*******************2+3 */
1990 // do not throw hrc here because we can't call i_finishTakingSnapshot() twice
1991 LogFlowThisFunc(("i_finishTakingSnapshot -> %Rhrc [mMachineState=%s]\n", hrc, ::stringifyMachineState(mData->mMachineState)));
1992 }
1993 catch (HRESULT hrcXcpt)
1994 {
1995 hrc = hrcXcpt;
1996 LogThisFunc(("Caught %Rhrc [mMachineState=%s]\n", hrc, ::stringifyMachineState(mData->mMachineState)));
1997
1998 /// @todo r=klaus check that the implicit diffs created above are cleaned up im the relevant error cases
1999
2000 /* preserve existing error info */
2001 ErrorInfoKeeper eik;
2002
2003 if (fBeganTakingSnapshot)
2004 i_finishTakingSnapshot(task, alock, false /*aSuccess*/);
2005
2006 // have to postpone this to the end as i_finishTakingSnapshot() needs
2007 // it for various cleanup steps
2008 if (task.m_pSnapshot)
2009 {
2010 task.m_pSnapshot->uninit();
2011 task.m_pSnapshot.setNull();
2012 }
2013 }
2014 Assert(alock.isWriteLockOnCurrentThread());
2015
2016 {
2017 // Keep all error information over the cleanup steps
2018 ErrorInfoKeeper eik;
2019
2020 /*
2021 * Fix up the machine state.
2022 *
2023 * For offline snapshots we just update the local copy, for the other
2024 * variants do the entire work. This ensures that the state is in sync
2025 * with the VM process (in particular the VM execution state).
2026 */
2027 bool fNeedClientMachineStateUpdate = false;
2028 if ( mData->mMachineState == MachineState_LiveSnapshotting
2029 || mData->mMachineState == MachineState_OnlineSnapshotting
2030 || mData->mMachineState == MachineState_Snapshotting)
2031 {
2032 if (!task.m_fTakingSnapshotOnline)
2033 i_setMachineState(task.m_machineStateBackup); /**************** 4 Machine::i_saveStateSettings*/
2034 else
2035 {
2036 MachineState_T enmMachineState = MachineState_Null;
2037 HRESULT hrc2 = task.m_pDirectControl->COMGETTER(NominalState)(&enmMachineState);
2038 if (FAILED(hrc2) || enmMachineState == MachineState_Null)
2039 {
2040 AssertMsgFailed(("state=%s\n", ::stringifyMachineState(enmMachineState)));
2041 // pure nonsense, try to continue somehow
2042 enmMachineState = MachineState_Aborted;
2043 }
2044 if (enmMachineState == MachineState_Paused)
2045 {
2046 if (fSuspendedBySave)
2047 {
2048 alock.release();
2049 hrc2 = task.m_pDirectControl->ResumeWithReason(Reason_Snapshot);
2050 alock.acquire();
2051 if (SUCCEEDED(hrc2))
2052 enmMachineState = task.m_machineStateBackup;
2053 }
2054 else
2055 enmMachineState = task.m_machineStateBackup;
2056 }
2057 if (enmMachineState != mData->mMachineState)
2058 {
2059 fNeedClientMachineStateUpdate = true;
2060 i_setMachineState(enmMachineState);
2061 }
2062 }
2063 }
2064
2065 /* check the remote state to see that we got it right. */
2066 MachineState_T enmMachineState = MachineState_Null;
2067 if (!task.m_pDirectControl.isNull())
2068 {
2069 ComPtr<IConsole> pConsole;
2070 task.m_pDirectControl->COMGETTER(RemoteConsole)(pConsole.asOutParam());
2071 if (!pConsole.isNull())
2072 pConsole->COMGETTER(State)(&enmMachineState);
2073 }
2074 LogFlowThisFunc(("local mMachineState=%s remote mMachineState=%s\n",
2075 ::stringifyMachineState(mData->mMachineState), ::stringifyMachineState(enmMachineState)));
2076
2077 if (fNeedClientMachineStateUpdate)
2078 i_updateMachineStateOnClient();
2079 }
2080
2081 task.m_pProgress->i_notifyComplete(hrc);
2082
2083 if (SUCCEEDED(hrc))
2084 mParent->i_onSnapshotTaken(mData->mUuid, task.m_uuidSnapshot);
2085
2086 if (SUCCEEDED(hrc))
2087 {
2088 for (std::map<Guid, DeviceType_T>::const_iterator it = uIdsForNotify.begin();
2089 it != uIdsForNotify.end();
2090 ++it)
2091 {
2092 mParent->i_onMediumRegistered(it->first, it->second, TRUE);
2093 }
2094
2095 for (std::set<ComObjPtr<Medium> >::const_iterator it = pMediaForNotify.begin();
2096 it != pMediaForNotify.end();
2097 ++it)
2098 {
2099 if (it->isNotNull())
2100 mParent->i_onMediumConfigChanged(*it);
2101 }
2102 }
2103 LogRel(("Finished taking snapshot %s\n", task.m_strName.c_str()));
2104 LogFlowThisFuncLeave();
2105}
2106
2107
2108/**
2109 * Progress cancelation callback employed by SessionMachine::i_takeSnapshotHandler.
2110 */
2111/*static*/
2112void SessionMachine::i_takeSnapshotProgressCancelCallback(void *pvUser)
2113{
2114 TakeSnapshotTask *pTask = (TakeSnapshotTask *)pvUser;
2115 AssertPtrReturnVoid(pTask);
2116 AssertReturnVoid(!pTask->m_pDirectControl.isNull());
2117 pTask->m_pDirectControl->CancelSaveStateWithReason();
2118}
2119
2120
2121/**
2122 * Called by the Console when it's done saving the VM state into the snapshot
2123 * (if online) and reconfiguring the hard disks. See BeginTakingSnapshot() above.
2124 *
2125 * This also gets called if the console part of snapshotting failed after the
2126 * BeginTakingSnapshot() call, to clean up the server side.
2127 *
2128 * @note Locks VirtualBox and this object for writing.
2129 *
2130 * @param task
2131 * @param alock
2132 * @param aSuccess Whether Console was successful with the client-side
2133 * snapshot things.
2134 * @return
2135 */
2136HRESULT SessionMachine::i_finishTakingSnapshot(TakeSnapshotTask &task, AutoWriteLock &alock, bool aSuccess)
2137{
2138 LogFlowThisFunc(("\n"));
2139
2140 Assert(alock.isWriteLockOnCurrentThread());
2141
2142 AssertReturn( !aSuccess
2143 || mData->mMachineState == MachineState_Snapshotting
2144 || mData->mMachineState == MachineState_OnlineSnapshotting
2145 || mData->mMachineState == MachineState_LiveSnapshotting, E_FAIL);
2146
2147 ComObjPtr<Snapshot> pOldFirstSnap = mData->mFirstSnapshot;
2148 ComObjPtr<Snapshot> pOldCurrentSnap = mData->mCurrentSnapshot;
2149
2150 HRESULT hrc = S_OK;
2151
2152 if (aSuccess)
2153 {
2154 // new snapshot becomes the current one
2155 mData->mCurrentSnapshot = task.m_pSnapshot;
2156
2157 /* memorize the first snapshot if necessary */
2158 if (!mData->mFirstSnapshot)
2159 mData->mFirstSnapshot = mData->mCurrentSnapshot;
2160
2161 int flSaveSettings = SaveS_Force; // do not do a deep compare in machine settings,
2162 // snapshots change, so we know we need to save
2163 if (!task.m_fTakingSnapshotOnline)
2164 /* the machine was powered off or saved when taking a snapshot, so
2165 * reset the mCurrentStateModified flag */
2166 flSaveSettings |= SaveS_ResetCurStateModified;
2167
2168 hrc = i_saveSettings(NULL, alock, flSaveSettings); /******************2 */
2169 }
2170
2171 if (aSuccess && SUCCEEDED(hrc))
2172 {
2173 /* associate old hard disks with the snapshot and do locking/unlocking*/
2174 i_commitMedia(task.m_fTakingSnapshotOnline);
2175 alock.release();
2176 }
2177 else
2178 {
2179 /* delete all differencing hard disks created (this will also attach
2180 * their parents back by rolling back mMediaData) */
2181 alock.release();
2182
2183 i_rollbackMedia();
2184
2185 mData->mFirstSnapshot = pOldFirstSnap; // might have been changed above
2186 mData->mCurrentSnapshot = pOldCurrentSnap; // might have been changed above
2187
2188 // delete the saved state file (it might have been already created)
2189 if (task.m_fTakingSnapshotOnline)
2190 // no need to test for whether the saved state file is shared: an online
2191 // snapshot means that a new saved state file was created, which we must
2192 // clean up now
2193 RTFileDelete(task.m_pSnapshot->i_getStateFilePath().c_str());
2194
2195 alock.acquire();
2196
2197 task.m_pSnapshot->uninit();
2198 alock.release();
2199
2200 }
2201
2202 /* clear out the snapshot data */
2203 task.m_pSnapshot.setNull();
2204
2205 /* alock has been released already */
2206 mParent->i_saveModifiedRegistries(); /**************3 */
2207
2208 alock.acquire();
2209
2210 return hrc;
2211}
2212
2213////////////////////////////////////////////////////////////////////////////////
2214//
2215// RestoreSnapshot methods (Machine and related tasks)
2216//
2217////////////////////////////////////////////////////////////////////////////////
2218
2219HRESULT Machine::restoreSnapshot(const ComPtr<ISnapshot> &aSnapshot,
2220 ComPtr<IProgress> &aProgress)
2221{
2222 NOREF(aSnapshot);
2223 NOREF(aProgress);
2224 ReturnComNotImplemented();
2225}
2226
2227/**
2228 * Restoring a snapshot happens entirely on the server side, the machine cannot be running.
2229 *
2230 * This creates a new thread that does the work and returns a progress object to the client.
2231 * Actual work then takes place in RestoreSnapshotTask::handler().
2232 *
2233 * @note Locks this + children objects for writing!
2234 *
2235 * @param aSnapshot in: the snapshot to restore.
2236 * @param aProgress out: progress object to monitor restore thread.
2237 * @return
2238 */
2239HRESULT SessionMachine::restoreSnapshot(const ComPtr<ISnapshot> &aSnapshot,
2240 ComPtr<IProgress> &aProgress)
2241{
2242 LogFlowThisFuncEnter();
2243
2244 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
2245
2246 // machine must not be running
2247 if (Global::IsOnlineOrTransient(mData->mMachineState))
2248 return setError(VBOX_E_INVALID_VM_STATE,
2249 tr("Cannot delete the current state of the running machine (machine state: %s)"),
2250 Global::stringifyMachineState(mData->mMachineState));
2251
2252 HRESULT hrc = i_checkStateDependency(MutableOrSavedStateDep);
2253 if (FAILED(hrc))
2254 return hrc;
2255
2256 /* We need to explicitly check if the given snapshot is valid and bail out if not. */
2257 if (aSnapshot.isNull())
2258 {
2259 if (aSnapshot == mData->mCurrentSnapshot)
2260 return setError(VBOX_E_OBJECT_NOT_FOUND,
2261 tr("This VM does not have any current snapshot"));
2262
2263 return setError(E_INVALIDARG, tr("The given snapshot is invalid"));
2264 }
2265
2266 ISnapshot* iSnapshot = aSnapshot;
2267 ComObjPtr<Snapshot> pSnapshot(static_cast<Snapshot*>(iSnapshot));
2268 ComObjPtr<SnapshotMachine> pSnapMachine = pSnapshot->i_getSnapshotMachine();
2269
2270 // create a progress object. The number of operations is:
2271 // 1 (preparing) + # of hard disks + 1 (if we need to copy the saved state file) */
2272 LogFlowThisFunc(("Going thru snapshot machine attachments to determine progress setup\n"));
2273
2274 ULONG ulOpCount = 1; // one for preparations
2275 ULONG ulTotalWeight = 1; // one for preparations
2276 for (MediumAttachmentList::iterator
2277 it = pSnapMachine->mMediumAttachments->begin();
2278 it != pSnapMachine->mMediumAttachments->end();
2279 ++it)
2280 {
2281 ComObjPtr<MediumAttachment> &pAttach = *it;
2282 AutoReadLock attachLock(pAttach COMMA_LOCKVAL_SRC_POS);
2283 if (pAttach->i_getType() == DeviceType_HardDisk)
2284 {
2285 ++ulOpCount;
2286 ++ulTotalWeight; // assume one MB weight for each differencing hard disk to manage
2287 Assert(pAttach->i_getMedium());
2288 LogFlowThisFunc(("op %d: considering hard disk attachment %s\n", ulOpCount,
2289 pAttach->i_getMedium()->i_getName().c_str()));
2290 }
2291 }
2292
2293 ComObjPtr<Progress> pProgress;
2294 pProgress.createObject();
2295 pProgress->init(mParent, static_cast<IMachine*>(this),
2296 BstrFmt(tr("Restoring snapshot '%s'"), pSnapshot->i_getName().c_str()).raw(),
2297 FALSE /* aCancelable */,
2298 ulOpCount,
2299 ulTotalWeight,
2300 Bstr(tr("Restoring machine settings")).raw(),
2301 1);
2302
2303 /* create and start the task on a separate thread (note that it will not
2304 * start working until we release alock) */
2305 RestoreSnapshotTask *pTask = new RestoreSnapshotTask(this,
2306 pProgress,
2307 "RestoreSnap",
2308 pSnapshot);
2309 hrc = pTask->createThread();
2310 pTask = NULL;
2311 if (FAILED(hrc))
2312 return hrc;
2313
2314 /* set the proper machine state (note: after creating a Task instance) */
2315 i_setMachineState(MachineState_RestoringSnapshot);
2316
2317 /* return the progress to the caller */
2318 pProgress.queryInterfaceTo(aProgress.asOutParam());
2319
2320 LogFlowThisFuncLeave();
2321
2322 return S_OK;
2323}
2324
2325/**
2326 * Worker method for the restore snapshot thread created by SessionMachine::RestoreSnapshot().
2327 * This method gets called indirectly through SessionMachine::taskHandler() which then
2328 * calls RestoreSnapshotTask::handler().
2329 *
2330 * The RestoreSnapshotTask contains the progress object returned to the console by
2331 * SessionMachine::RestoreSnapshot, through which progress and results are reported.
2332 *
2333 * @note Locks mParent + this object for writing.
2334 *
2335 * @param task Task data.
2336 */
2337void SessionMachine::i_restoreSnapshotHandler(RestoreSnapshotTask &task)
2338{
2339 LogFlowThisFuncEnter();
2340
2341 AutoCaller autoCaller(this);
2342
2343 LogFlowThisFunc(("state=%d\n", getObjectState().getState()));
2344 if (!autoCaller.isOk())
2345 {
2346 /* we might have been uninitialized because the session was accidentally
2347 * closed by the client, so don't assert */
2348 task.m_pProgress->i_notifyComplete(E_FAIL,
2349 COM_IIDOF(IMachine),
2350 getComponentName(),
2351 tr("The session has been accidentally closed"));
2352
2353 LogFlowThisFuncLeave();
2354 return;
2355 }
2356
2357 HRESULT hrc = S_OK;
2358 Guid snapshotId;
2359 std::set<ComObjPtr<Medium> > pMediaForNotify;
2360 std::map<Guid, std::pair<DeviceType_T, BOOL> > uIdsForNotify;
2361
2362 try
2363 {
2364 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
2365
2366 /* Discard all current changes to mUserData (name, OSType etc.).
2367 * Note that the machine is powered off, so there is no need to inform
2368 * the direct session. */
2369 if (mData->flModifications)
2370 i_rollback(false /* aNotify */);
2371
2372 /* Delete the saved state file if the machine was Saved prior to this
2373 * operation */
2374 if (task.m_machineStateBackup == MachineState_Saved || task.m_machineStateBackup == MachineState_AbortedSaved)
2375 {
2376 Assert(!mSSData->strStateFilePath.isEmpty());
2377
2378 // release the saved state file AFTER unsetting the member variable
2379 // so that releaseSavedStateFile() won't think it's still in use
2380 Utf8Str strStateFile(mSSData->strStateFilePath);
2381 mSSData->strStateFilePath.setNull();
2382 i_releaseSavedStateFile(strStateFile, NULL /* pSnapshotToIgnore */ );
2383
2384 task.modifyBackedUpState(MachineState_PoweredOff);
2385
2386 hrc = i_saveStateSettings(SaveSTS_StateFilePath);
2387 if (FAILED(hrc))
2388 throw hrc;
2389 }
2390
2391 RTTIMESPEC snapshotTimeStamp;
2392 RTTimeSpecSetMilli(&snapshotTimeStamp, 0);
2393
2394 {
2395 AutoReadLock snapshotLock(task.m_pSnapshot COMMA_LOCKVAL_SRC_POS);
2396
2397 /* remember the timestamp of the snapshot we're restoring from */
2398 snapshotTimeStamp = task.m_pSnapshot->i_getTimeStamp();
2399
2400 // save the snapshot ID (paranoia, here we hold the lock)
2401 snapshotId = task.m_pSnapshot->i_getId();
2402
2403 ComPtr<SnapshotMachine> pSnapshotMachine(task.m_pSnapshot->i_getSnapshotMachine());
2404
2405 /* copy all hardware data from the snapshot */
2406 i_copyFrom(pSnapshotMachine);
2407
2408 LogFlowThisFunc(("Restoring hard disks from the snapshot...\n"));
2409
2410 // restore the attachments from the snapshot
2411 i_setModified(IsModified_Storage);
2412 mMediumAttachments.backup();
2413 mMediumAttachments->clear();
2414 for (MediumAttachmentList::const_iterator
2415 it = pSnapshotMachine->mMediumAttachments->begin();
2416 it != pSnapshotMachine->mMediumAttachments->end();
2417 ++it)
2418 {
2419 ComObjPtr<MediumAttachment> pAttach;
2420 pAttach.createObject();
2421 pAttach->initCopy(this, *it);
2422 mMediumAttachments->push_back(pAttach);
2423 }
2424
2425 /* release the locks before the potentially lengthy operation */
2426 snapshotLock.release();
2427 alock.release();
2428
2429 hrc = i_createImplicitDiffs(task.m_pProgress, 1, false /* aOnline */);
2430 if (FAILED(hrc))
2431 throw hrc;
2432
2433 alock.acquire();
2434 snapshotLock.acquire();
2435
2436 /* Note: on success, current (old) hard disks will be
2437 * deassociated/deleted on #commit() called from #i_saveSettings() at
2438 * the end. On failure, newly created implicit diffs will be
2439 * deleted by #rollback() at the end. */
2440
2441 /* should not have a saved state file associated at this point */
2442 Assert(mSSData->strStateFilePath.isEmpty());
2443
2444 const Utf8Str &strSnapshotStateFile = task.m_pSnapshot->i_getStateFilePath();
2445
2446 if (strSnapshotStateFile.isNotEmpty())
2447 // online snapshot: then share the state file
2448 mSSData->strStateFilePath = strSnapshotStateFile;
2449
2450 const Utf8Str srcNVRAM(pSnapshotMachine->mNvramStore->i_getNonVolatileStorageFile());
2451 const Utf8Str dstNVRAM(mNvramStore->i_getNonVolatileStorageFile());
2452 if (dstNVRAM.isNotEmpty() && RTFileExists(dstNVRAM.c_str()))
2453 RTFileDelete(dstNVRAM.c_str());
2454 if (srcNVRAM.isNotEmpty() && dstNVRAM.isNotEmpty() && RTFileExists(srcNVRAM.c_str()))
2455 RTFileCopy(srcNVRAM.c_str(), dstNVRAM.c_str());
2456
2457 LogFlowThisFunc(("Setting new current snapshot {%RTuuid}\n", task.m_pSnapshot->i_getId().raw()));
2458 /* make the snapshot we restored from the current snapshot */
2459 mData->mCurrentSnapshot = task.m_pSnapshot;
2460 }
2461
2462 // store parent of newly created diffs for notify
2463 {
2464 MediumAttachmentList &oldAtts = *mMediumAttachments.backedUpData();
2465 for (MediumAttachmentList::const_iterator
2466 it = mMediumAttachments->begin();
2467 it != mMediumAttachments->end();
2468 ++it)
2469 {
2470 MediumAttachment *pAttach = *it;
2471 Medium *pMedium = pAttach->i_getMedium();
2472 if (!pMedium)
2473 continue;
2474
2475 bool fFound = false;
2476 /* was this medium attached before? */
2477 for (MediumAttachmentList::iterator
2478 oldIt = oldAtts.begin();
2479 oldIt != oldAtts.end();
2480 ++oldIt)
2481 {
2482 MediumAttachment *pOldAttach = *oldIt;
2483 if (pOldAttach->i_getMedium() == pMedium)
2484 {
2485 fFound = true;
2486 break;
2487 }
2488 }
2489 if (!fFound)
2490 {
2491 pMediaForNotify.insert(pMedium->i_getParent());
2492 uIdsForNotify[pMedium->i_getId()] = std::pair<DeviceType_T, BOOL>(pMedium->i_getDeviceType(), TRUE);
2493 }
2494 }
2495 }
2496
2497 /* grab differencing hard disks from the old attachments that will
2498 * become unused and need to be auto-deleted */
2499 std::list< ComObjPtr<MediumAttachment> > llDiffAttachmentsToDelete;
2500
2501 for (MediumAttachmentList::const_iterator
2502 it = mMediumAttachments.backedUpData()->begin();
2503 it != mMediumAttachments.backedUpData()->end();
2504 ++it)
2505 {
2506 ComObjPtr<MediumAttachment> pAttach = *it;
2507 ComObjPtr<Medium> pMedium = pAttach->i_getMedium();
2508
2509 /* while the hard disk is attached, the number of children or the
2510 * parent cannot change, so no lock */
2511 if ( !pMedium.isNull()
2512 && pAttach->i_getType() == DeviceType_HardDisk
2513 && !pMedium->i_getParent().isNull()
2514 && pMedium->i_getChildren().size() == 0
2515 )
2516 {
2517 LogFlowThisFunc(("Picked differencing image '%s' for deletion\n", pMedium->i_getName().c_str()));
2518
2519 llDiffAttachmentsToDelete.push_back(pAttach);
2520 }
2521 }
2522
2523 /* we have already deleted the current state, so set the execution
2524 * state accordingly no matter of the delete snapshot result */
2525 if (mSSData->strStateFilePath.isNotEmpty())
2526 task.modifyBackedUpState(MachineState_Saved);
2527 else
2528 task.modifyBackedUpState(MachineState_PoweredOff);
2529
2530 /* Paranoia: no one must have saved the settings in the mean time. If
2531 * it happens nevertheless we'll close our eyes and continue below. */
2532 Assert(mMediumAttachments.isBackedUp());
2533
2534 /* assign the timestamp from the snapshot */
2535 Assert(RTTimeSpecGetMilli(&snapshotTimeStamp) != 0);
2536 mData->mLastStateChange = snapshotTimeStamp;
2537
2538 // detach the current-state diffs that we detected above and build a list of
2539 // image files to delete _after_ i_saveSettings()
2540
2541 MediaList llDiffsToDelete;
2542
2543 for (std::list< ComObjPtr<MediumAttachment> >::iterator it = llDiffAttachmentsToDelete.begin();
2544 it != llDiffAttachmentsToDelete.end();
2545 ++it)
2546 {
2547 ComObjPtr<MediumAttachment> pAttach = *it; // guaranteed to have only attachments where medium != NULL
2548 ComObjPtr<Medium> pMedium = pAttach->i_getMedium();
2549
2550 AutoWriteLock mlock(pMedium COMMA_LOCKVAL_SRC_POS);
2551
2552 LogFlowThisFunc(("Detaching old current state in differencing image '%s'\n", pMedium->i_getName().c_str()));
2553
2554 // Normally we "detach" the medium by removing the attachment object
2555 // from the current machine data; i_saveSettings() below would then
2556 // compare the current machine data with the one in the backup
2557 // and actually call Medium::removeBackReference(). But that works only half
2558 // the time in our case so instead we force a detachment here:
2559 // remove from machine data
2560 mMediumAttachments->remove(pAttach);
2561 // Remove it from the backup or else i_saveSettings will try to detach
2562 // it again and assert. The paranoia check avoids crashes (see
2563 // assert above) if this code is buggy and saves settings in the
2564 // wrong place.
2565 if (mMediumAttachments.isBackedUp())
2566 mMediumAttachments.backedUpData()->remove(pAttach);
2567 // then clean up backrefs
2568 pMedium->i_removeBackReference(mData->mUuid);
2569
2570 llDiffsToDelete.push_back(pMedium);
2571 }
2572
2573 // save machine settings, reset the modified flag and commit;
2574 bool fNeedsGlobalSaveSettings = false;
2575 hrc = i_saveSettings(&fNeedsGlobalSaveSettings, alock, SaveS_ResetCurStateModified);
2576 if (FAILED(hrc))
2577 throw hrc;
2578
2579 // release the locks before updating registry and deleting image files
2580 alock.release();
2581
2582 // unconditionally add the parent registry.
2583 mParent->i_markRegistryModified(mParent->i_getGlobalRegistryId());
2584
2585 // from here on we cannot roll back on failure any more
2586
2587 for (MediaList::iterator it = llDiffsToDelete.begin();
2588 it != llDiffsToDelete.end();
2589 ++it)
2590 {
2591 ComObjPtr<Medium> &pMedium = *it;
2592 LogFlowThisFunc(("Deleting old current state in differencing image '%s'\n", pMedium->i_getName().c_str()));
2593
2594 ComObjPtr<Medium> pParent = pMedium->i_getParent();
2595 // store the id here because it becomes NULL after deleting storage.
2596 com::Guid id = pMedium->i_getId();
2597 HRESULT hrc2 = pMedium->i_deleteStorage(NULL /* aProgress */, true /* aWait */, false /* aNotify */);
2598 // ignore errors here because we cannot roll back after i_saveSettings() above
2599 if (SUCCEEDED(hrc2))
2600 {
2601 pMediaForNotify.insert(pParent);
2602 uIdsForNotify[id] = std::pair<DeviceType_T, BOOL>(pMedium->i_getDeviceType(), FALSE);
2603 pMedium->uninit();
2604 }
2605 }
2606 }
2607 catch (HRESULT hrcXcpt)
2608 {
2609 hrc = hrcXcpt;
2610 }
2611
2612 if (FAILED(hrc))
2613 {
2614 /* preserve existing error info */
2615 ErrorInfoKeeper eik;
2616
2617 /* If any MediumAttachments have been modified then Machine::i_rollback() will
2618 * go through Machine::i_rollbackMedia() -> Machine::i_deleteImplicitDiffs() ->
2619 * Medium::i_deleteStorage() -> Medium::i_markRegistriesModified() ->
2620 * VirtualBox::i_markRegistryModified() -> VirtualBox::i_findMachine() which
2621 * attempts to acquire the Machine object lock for 'VirtualBox::allMachines' but
2622 * the locking order when accessing the MachinesOList type (aka ObjectsList<Machine>)
2623 * requires that the machines lock list (LOCKCLASS_LISTOFMACHINES) be
2624 * acquired first and then the Machine object lock (LOCKCLASS_MACHINEOBJECT).
2625 */
2626 AutoReadLock alockMachines(mParent->i_getMachinesListLockHandle() COMMA_LOCKVAL_SRC_POS);
2627
2628 /* undo all changes on failure */
2629 i_rollback(false /* aNotify */);
2630 }
2631
2632 mParent->i_saveModifiedRegistries();
2633
2634 /* restore the machine state */
2635 i_setMachineState(task.m_machineStateBackup);
2636
2637 /* set the result (this will try to fetch current error info on failure) */
2638 task.m_pProgress->i_notifyComplete(hrc);
2639
2640 if (SUCCEEDED(hrc))
2641 {
2642 mParent->i_onSnapshotRestored(mData->mUuid, snapshotId);
2643 for (std::map<Guid, std::pair<DeviceType_T, BOOL> >::const_iterator it = uIdsForNotify.begin();
2644 it != uIdsForNotify.end();
2645 ++it)
2646 {
2647 mParent->i_onMediumRegistered(it->first, it->second.first, it->second.second);
2648 }
2649 for (std::set<ComObjPtr<Medium> >::const_iterator it = pMediaForNotify.begin();
2650 it != pMediaForNotify.end();
2651 ++it)
2652 {
2653 if (it->isNotNull())
2654 mParent->i_onMediumConfigChanged(*it);
2655 }
2656 }
2657
2658 LogFlowThisFunc(("Done restoring snapshot (hrc=%08X)\n", hrc));
2659
2660 LogFlowThisFuncLeave();
2661}
2662
2663////////////////////////////////////////////////////////////////////////////////
2664//
2665// DeleteSnapshot methods (SessionMachine and related tasks)
2666//
2667////////////////////////////////////////////////////////////////////////////////
2668
2669HRESULT Machine::deleteSnapshot(const com::Guid &aId, ComPtr<IProgress> &aProgress)
2670{
2671 NOREF(aId);
2672 NOREF(aProgress);
2673 ReturnComNotImplemented();
2674}
2675
2676HRESULT SessionMachine::deleteSnapshot(const com::Guid &aId, ComPtr<IProgress> &aProgress)
2677{
2678 return i_deleteSnapshot(aId, aId,
2679 FALSE /* fDeleteAllChildren */,
2680 aProgress);
2681}
2682
2683HRESULT Machine::deleteSnapshotAndAllChildren(const com::Guid &aId, ComPtr<IProgress> &aProgress)
2684{
2685 NOREF(aId);
2686 NOREF(aProgress);
2687 ReturnComNotImplemented();
2688}
2689
2690HRESULT SessionMachine::deleteSnapshotAndAllChildren(const com::Guid &aId, ComPtr<IProgress> &aProgress)
2691{
2692 return i_deleteSnapshot(aId, aId,
2693 TRUE /* fDeleteAllChildren */,
2694 aProgress);
2695}
2696
2697HRESULT Machine::deleteSnapshotRange(const com::Guid &aStartId, const com::Guid &aEndId, ComPtr<IProgress> &aProgress)
2698{
2699 NOREF(aStartId);
2700 NOREF(aEndId);
2701 NOREF(aProgress);
2702 ReturnComNotImplemented();
2703}
2704
2705HRESULT SessionMachine::deleteSnapshotRange(const com::Guid &aStartId, const com::Guid &aEndId, ComPtr<IProgress> &aProgress)
2706{
2707 return i_deleteSnapshot(aStartId, aEndId,
2708 FALSE /* fDeleteAllChildren */,
2709 aProgress);
2710}
2711
2712
2713/**
2714 * Implementation for SessionMachine::i_deleteSnapshot().
2715 *
2716 * Gets called from SessionMachine::DeleteSnapshot(). Deleting a snapshot
2717 * happens entirely on the server side if the machine is not running, and
2718 * if it is running then the merges are done via internal session callbacks.
2719 *
2720 * This creates a new thread that does the work and returns a progress
2721 * object to the client.
2722 *
2723 * Actual work then takes place in SessionMachine::i_deleteSnapshotHandler().
2724 *
2725 * @note Locks mParent + this + children objects for writing!
2726 */
2727HRESULT SessionMachine::i_deleteSnapshot(const com::Guid &aStartId,
2728 const com::Guid &aEndId,
2729 BOOL aDeleteAllChildren,
2730 ComPtr<IProgress> &aProgress)
2731{
2732 LogFlowThisFuncEnter();
2733
2734 AssertReturn(!aStartId.isZero() && !aEndId.isZero() && aStartId.isValid() && aEndId.isValid(), E_INVALIDARG);
2735
2736 /** @todo implement the "and all children" and "range" variants */
2737 if (aDeleteAllChildren || aStartId != aEndId)
2738 ReturnComNotImplemented();
2739
2740 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
2741
2742 if (Global::IsTransient(mData->mMachineState))
2743 return setError(VBOX_E_INVALID_VM_STATE,
2744 tr("Cannot delete a snapshot of the machine while it is changing the state (machine state: %s)"),
2745 Global::stringifyMachineState(mData->mMachineState));
2746
2747 // be very picky about machine states
2748 if ( Global::IsOnlineOrTransient(mData->mMachineState)
2749 && mData->mMachineState != MachineState_PoweredOff
2750 && mData->mMachineState != MachineState_Saved
2751 && mData->mMachineState != MachineState_Teleported
2752 && mData->mMachineState != MachineState_Aborted
2753 && mData->mMachineState != MachineState_AbortedSaved
2754 && mData->mMachineState != MachineState_Running
2755 && mData->mMachineState != MachineState_Paused)
2756 return setError(VBOX_E_INVALID_VM_STATE,
2757 tr("Invalid machine state: %s"),
2758 Global::stringifyMachineState(mData->mMachineState));
2759
2760 HRESULT hrc = i_checkStateDependency(MutableOrSavedOrRunningStateDep);
2761 if (FAILED(hrc))
2762 return hrc;
2763
2764 ComObjPtr<Snapshot> pSnapshot;
2765 hrc = i_findSnapshotById(aStartId, pSnapshot, true /* aSetError */);
2766 if (FAILED(hrc))
2767 return hrc;
2768
2769 AutoWriteLock snapshotLock(pSnapshot COMMA_LOCKVAL_SRC_POS);
2770 Utf8Str str;
2771
2772 size_t childrenCount = pSnapshot->i_getChildrenCount();
2773 if (childrenCount > 1)
2774 return setError(VBOX_E_INVALID_OBJECT_STATE,
2775 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",
2776 "", childrenCount),
2777 pSnapshot->i_getName().c_str(),
2778 mUserData->s.strName.c_str(),
2779 childrenCount);
2780
2781 if (pSnapshot == mData->mCurrentSnapshot && childrenCount >= 1)
2782 return setError(VBOX_E_INVALID_OBJECT_STATE,
2783 tr("Snapshot '%s' of the machine '%s' cannot be deleted, because it is the current snapshot and has one child snapshot"),
2784 pSnapshot->i_getName().c_str(),
2785 mUserData->s.strName.c_str());
2786
2787 /* If the snapshot being deleted is the current one, ensure current
2788 * settings are committed and saved.
2789 */
2790 if (pSnapshot == mData->mCurrentSnapshot)
2791 {
2792 if (mData->flModifications)
2793 {
2794 snapshotLock.release();
2795 hrc = i_saveSettings(NULL, alock);
2796 snapshotLock.acquire();
2797 // no need to change for whether VirtualBox.xml needs saving since
2798 // we can't have a machine XML rename pending at this point
2799 if (FAILED(hrc)) return hrc;
2800 }
2801 }
2802
2803 ComObjPtr<SnapshotMachine> pSnapMachine = pSnapshot->i_getSnapshotMachine();
2804
2805 /* create a progress object. The number of operations is:
2806 * 1 (preparing) + 1 if the snapshot is online + # of normal hard disks
2807 */
2808 LogFlowThisFunc(("Going thru snapshot machine attachments to determine progress setup\n"));
2809
2810 ULONG ulOpCount = 1; // one for preparations
2811 ULONG ulTotalWeight = 1; // one for preparations
2812
2813 if (pSnapshot->i_getStateFilePath().isNotEmpty())
2814 {
2815 ++ulOpCount;
2816 ++ulTotalWeight; // assume 1 MB for deleting the state file
2817 }
2818
2819 bool fDeleteOnline = mData->mMachineState == MachineState_Running || mData->mMachineState == MachineState_Paused;
2820
2821 // count normal hard disks and add their sizes to the weight
2822 for (MediumAttachmentList::iterator
2823 it = pSnapMachine->mMediumAttachments->begin();
2824 it != pSnapMachine->mMediumAttachments->end();
2825 ++it)
2826 {
2827 ComObjPtr<MediumAttachment> &pAttach = *it;
2828 AutoReadLock attachLock(pAttach COMMA_LOCKVAL_SRC_POS);
2829 if (pAttach->i_getType() == DeviceType_HardDisk)
2830 {
2831 ComObjPtr<Medium> pHD = pAttach->i_getMedium();
2832 Assert(pHD);
2833 AutoReadLock mlock(pHD COMMA_LOCKVAL_SRC_POS);
2834
2835 MediumType_T type = pHD->i_getType();
2836 // writethrough and shareable images are unaffected by snapshots,
2837 // so do nothing for them
2838 if ( type != MediumType_Writethrough
2839 && type != MediumType_Shareable
2840 && type != MediumType_Readonly)
2841 {
2842 // normal or immutable media need attention
2843 ++ulOpCount;
2844 // offline merge includes medium resizing
2845 if (!fDeleteOnline)
2846 ++ulOpCount;
2847 ulTotalWeight += (ULONG)(pHD->i_getSize() / _1M);
2848 }
2849 LogFlowThisFunc(("op %d: considering hard disk attachment %s\n", ulOpCount, pHD->i_getName().c_str()));
2850 }
2851 }
2852
2853 ComObjPtr<Progress> pProgress;
2854 pProgress.createObject();
2855 pProgress->init(mParent, static_cast<IMachine*>(this),
2856 BstrFmt(tr("Deleting snapshot '%s'"), pSnapshot->i_getName().c_str()).raw(),
2857 FALSE /* aCancelable */,
2858 ulOpCount,
2859 ulTotalWeight,
2860 Bstr(tr("Setting up")).raw(),
2861 1);
2862
2863 /* create and start the task on a separate thread */
2864 DeleteSnapshotTask *pTask = new DeleteSnapshotTask(this, pProgress,
2865 "DeleteSnap",
2866 fDeleteOnline,
2867 pSnapshot);
2868 hrc = pTask->createThread();
2869 pTask = NULL;
2870 if (FAILED(hrc))
2871 return hrc;
2872
2873 // the task might start running but will block on acquiring the machine's write lock
2874 // which we acquired above; once this function leaves, the task will be unblocked;
2875 // set the proper machine state here now (note: after creating a Task instance)
2876 if (mData->mMachineState == MachineState_Running)
2877 {
2878 i_setMachineState(MachineState_DeletingSnapshotOnline);
2879 i_updateMachineStateOnClient();
2880 }
2881 else if (mData->mMachineState == MachineState_Paused)
2882 {
2883 i_setMachineState(MachineState_DeletingSnapshotPaused);
2884 i_updateMachineStateOnClient();
2885 }
2886 else
2887 i_setMachineState(MachineState_DeletingSnapshot);
2888
2889 /* return the progress to the caller */
2890 pProgress.queryInterfaceTo(aProgress.asOutParam());
2891
2892 LogFlowThisFuncLeave();
2893
2894 return S_OK;
2895}
2896
2897/**
2898 * Helper struct for SessionMachine::deleteSnapshotHandler().
2899 */
2900struct MediumDeleteRec
2901{
2902 MediumDeleteRec()
2903 : mfNeedsOnlineMerge(false),
2904 mpMediumLockList(NULL)
2905 {}
2906
2907 MediumDeleteRec(const ComObjPtr<Medium> &aHd,
2908 const ComObjPtr<Medium> &aSource,
2909 const ComObjPtr<Medium> &aTarget,
2910 const ComObjPtr<MediumAttachment> &aOnlineMediumAttachment,
2911 bool fMergeForward,
2912 const ComObjPtr<Medium> &aParentForTarget,
2913 MediumLockList *aChildrenToReparent,
2914 bool fNeedsOnlineMerge,
2915 MediumLockList *aMediumLockList,
2916 const ComPtr<IToken> &aHDLockToken)
2917 : mpHD(aHd),
2918 mpSource(aSource),
2919 mpTarget(aTarget),
2920 mpOnlineMediumAttachment(aOnlineMediumAttachment),
2921 mfMergeForward(fMergeForward),
2922 mpParentForTarget(aParentForTarget),
2923 mpChildrenToReparent(aChildrenToReparent),
2924 mfNeedsOnlineMerge(fNeedsOnlineMerge),
2925 mpMediumLockList(aMediumLockList),
2926 mpHDLockToken(aHDLockToken)
2927 {}
2928
2929 MediumDeleteRec(const ComObjPtr<Medium> &aHd,
2930 const ComObjPtr<Medium> &aSource,
2931 const ComObjPtr<Medium> &aTarget,
2932 const ComObjPtr<MediumAttachment> &aOnlineMediumAttachment,
2933 bool fMergeForward,
2934 const ComObjPtr<Medium> &aParentForTarget,
2935 MediumLockList *aChildrenToReparent,
2936 bool fNeedsOnlineMerge,
2937 MediumLockList *aMediumLockList,
2938 const ComPtr<IToken> &aHDLockToken,
2939 const Guid &aMachineId,
2940 const Guid &aSnapshotId)
2941 : mpHD(aHd),
2942 mpSource(aSource),
2943 mpTarget(aTarget),
2944 mpOnlineMediumAttachment(aOnlineMediumAttachment),
2945 mfMergeForward(fMergeForward),
2946 mpParentForTarget(aParentForTarget),
2947 mpChildrenToReparent(aChildrenToReparent),
2948 mfNeedsOnlineMerge(fNeedsOnlineMerge),
2949 mpMediumLockList(aMediumLockList),
2950 mpHDLockToken(aHDLockToken),
2951 mMachineId(aMachineId),
2952 mSnapshotId(aSnapshotId)
2953 {}
2954
2955 ComObjPtr<Medium> mpHD;
2956 ComObjPtr<Medium> mpSource;
2957 ComObjPtr<Medium> mpTarget;
2958 ComObjPtr<MediumAttachment> mpOnlineMediumAttachment;
2959 bool mfMergeForward;
2960 ComObjPtr<Medium> mpParentForTarget;
2961 MediumLockList *mpChildrenToReparent;
2962 bool mfNeedsOnlineMerge;
2963 MediumLockList *mpMediumLockList;
2964 /** optional lock token, used only in case mpHD is not merged/deleted */
2965 ComPtr<IToken> mpHDLockToken;
2966 /* these are for reattaching the hard disk in case of a failure: */
2967 Guid mMachineId;
2968 Guid mSnapshotId;
2969};
2970
2971typedef std::list<MediumDeleteRec> MediumDeleteRecList;
2972
2973/**
2974 * Worker method for the delete snapshot thread created by
2975 * SessionMachine::DeleteSnapshot(). This method gets called indirectly
2976 * through SessionMachine::taskHandler() which then calls
2977 * DeleteSnapshotTask::handler().
2978 *
2979 * The DeleteSnapshotTask contains the progress object returned to the console
2980 * by SessionMachine::DeleteSnapshot, through which progress and results are
2981 * reported.
2982 *
2983 * SessionMachine::DeleteSnapshot() has set the machine state to
2984 * MachineState_DeletingSnapshot right after creating this task. Since we block
2985 * on the machine write lock at the beginning, once that has been acquired, we
2986 * can assume that the machine state is indeed that.
2987 *
2988 * @note Locks the machine + the snapshot + the media tree for writing!
2989 *
2990 * @param task Task data.
2991 */
2992void SessionMachine::i_deleteSnapshotHandler(DeleteSnapshotTask &task)
2993{
2994 LogFlowThisFuncEnter();
2995
2996 MultiResult mrc(S_OK);
2997 AutoCaller autoCaller(this);
2998 LogFlowThisFunc(("state=%d\n", getObjectState().getState()));
2999 if (FAILED(autoCaller.hrc()))
3000 {
3001 /* we might have been uninitialized because the session was accidentally
3002 * closed by the client, so don't assert */
3003 mrc = setError(E_FAIL,
3004 tr("The session has been accidentally closed"));
3005 task.m_pProgress->i_notifyComplete(mrc);
3006 LogFlowThisFuncLeave();
3007 return;
3008 }
3009
3010 MediumDeleteRecList toDelete;
3011 Guid snapshotId;
3012 std::set<ComObjPtr<Medium> > pMediaForNotify;
3013 std::map<Guid,DeviceType_T> uIdsForNotify;
3014
3015 try
3016 {
3017 HRESULT hrc = S_OK;
3018
3019 /* Locking order: */
3020 AutoMultiWriteLock2 multiLock(this->lockHandle(), // machine
3021 task.m_pSnapshot->lockHandle() // snapshot
3022 COMMA_LOCKVAL_SRC_POS);
3023 // once we have this lock, we know that SessionMachine::DeleteSnapshot()
3024 // has exited after setting the machine state to MachineState_DeletingSnapshot
3025
3026 AutoWriteLock treeLock(mParent->i_getMediaTreeLockHandle()
3027 COMMA_LOCKVAL_SRC_POS);
3028
3029 ComObjPtr<SnapshotMachine> pSnapMachine = task.m_pSnapshot->i_getSnapshotMachine();
3030 // no need to lock the snapshot machine since it is const by definition
3031 Guid machineId = pSnapMachine->i_getId();
3032
3033 // save the snapshot ID (for callbacks)
3034 snapshotId = task.m_pSnapshot->i_getId();
3035
3036 // first pass:
3037 LogFlowThisFunc(("1: Checking hard disk merge prerequisites...\n"));
3038
3039 // Go thru the attachments of the snapshot machine (the media in here
3040 // point to the disk states _before_ the snapshot was taken, i.e. the
3041 // state we're restoring to; for each such medium, we will need to
3042 // merge it with its one and only child (the diff image holding the
3043 // changes written after the snapshot was taken).
3044 for (MediumAttachmentList::iterator
3045 it = pSnapMachine->mMediumAttachments->begin();
3046 it != pSnapMachine->mMediumAttachments->end();
3047 ++it)
3048 {
3049 ComObjPtr<MediumAttachment> &pAttach = *it;
3050 AutoReadLock attachLock(pAttach COMMA_LOCKVAL_SRC_POS);
3051 if (pAttach->i_getType() != DeviceType_HardDisk)
3052 continue;
3053
3054 ComObjPtr<Medium> pHD = pAttach->i_getMedium();
3055 Assert(!pHD.isNull());
3056
3057 {
3058 // writethrough, shareable and readonly images are
3059 // unaffected by snapshots, skip them
3060 AutoReadLock medlock(pHD COMMA_LOCKVAL_SRC_POS);
3061 MediumType_T type = pHD->i_getType();
3062 if ( type == MediumType_Writethrough
3063 || type == MediumType_Shareable
3064 || type == MediumType_Readonly)
3065 continue;
3066 }
3067
3068#ifdef DEBUG
3069 pHD->i_dumpBackRefs();
3070#endif
3071
3072 // needs to be merged with child or deleted, check prerequisites
3073 ComObjPtr<Medium> pTarget;
3074 ComObjPtr<Medium> pSource;
3075 bool fMergeForward = false;
3076 ComObjPtr<Medium> pParentForTarget;
3077 MediumLockList *pChildrenToReparent = NULL;
3078 bool fNeedsOnlineMerge = false;
3079 bool fOnlineMergePossible = task.m_fDeleteOnline;
3080 MediumLockList *pMediumLockList = NULL;
3081 MediumLockList *pVMMALockList = NULL;
3082 ComPtr<IToken> pHDLockToken;
3083 ComObjPtr<MediumAttachment> pOnlineMediumAttachment;
3084 if (fOnlineMergePossible)
3085 {
3086 // Look up the corresponding medium attachment in the currently
3087 // running VM. Any failure prevents a live merge. Could be made
3088 // a tad smarter by trying a few candidates, so that e.g. disks
3089 // which are simply moved to a different controller slot do not
3090 // prevent online merging in general.
3091 pOnlineMediumAttachment =
3092 i_findAttachment(*mMediumAttachments.data(),
3093 pAttach->i_getControllerName(),
3094 pAttach->i_getPort(),
3095 pAttach->i_getDevice());
3096 if (pOnlineMediumAttachment)
3097 {
3098 hrc = mData->mSession.mLockedMedia.Get(pOnlineMediumAttachment, pVMMALockList);
3099 if (FAILED(hrc))
3100 fOnlineMergePossible = false;
3101 }
3102 else
3103 fOnlineMergePossible = false;
3104 }
3105
3106 // no need to hold the lock any longer
3107 attachLock.release();
3108
3109 treeLock.release();
3110 hrc = i_prepareDeleteSnapshotMedium(pHD, machineId, snapshotId,
3111 fOnlineMergePossible,
3112 pVMMALockList, pSource, pTarget,
3113 fMergeForward, pParentForTarget,
3114 pChildrenToReparent,
3115 fNeedsOnlineMerge,
3116 pMediumLockList,
3117 pHDLockToken);
3118 treeLock.acquire();
3119 if (FAILED(hrc))
3120 throw hrc;
3121
3122 // For simplicity, prepareDeleteSnapshotMedium selects the merge
3123 // direction in the following way: we merge pHD onto its child
3124 // (forward merge), not the other way round, because that saves us
3125 // from unnecessarily shuffling around the attachments for the
3126 // machine that follows the snapshot (next snapshot or current
3127 // state), unless it's a base image. Backwards merges of the first
3128 // snapshot into the base image is essential, as it ensures that
3129 // when all snapshots are deleted the only remaining image is a
3130 // base image. Important e.g. for medium formats which do not have
3131 // a file representation such as iSCSI.
3132
3133 // not going to merge a big source into a small target on online merge. Otherwise it will be resized
3134 if (fNeedsOnlineMerge && pSource->i_getLogicalSize() > pTarget->i_getLogicalSize())
3135 throw setError(E_FAIL,
3136 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",
3137 "", pSource->i_getLogicalSize()),
3138 pTarget->i_getLocationFull().c_str(), pSource->i_getLogicalSize());
3139
3140 // a couple paranoia checks for backward merges
3141 if (pMediumLockList != NULL && !fMergeForward)
3142 {
3143 // parent is null -> this disk is a base hard disk: we will
3144 // then do a backward merge, i.e. merge its only child onto the
3145 // base disk. Here we need then to update the attachment that
3146 // refers to the child and have it point to the parent instead
3147 Assert(pHD->i_getChildren().size() == 1);
3148
3149 ComObjPtr<Medium> pReplaceHD = pHD->i_getChildren().front();
3150
3151 ComAssertThrow(pReplaceHD == pSource, E_FAIL);
3152 }
3153
3154 Guid replaceMachineId;
3155 Guid replaceSnapshotId;
3156
3157 const Guid *pReplaceMachineId = pSource->i_getFirstMachineBackrefId();
3158 // minimal sanity checking
3159 Assert(!pReplaceMachineId || *pReplaceMachineId == mData->mUuid);
3160 if (pReplaceMachineId)
3161 replaceMachineId = *pReplaceMachineId;
3162
3163 const Guid *pSnapshotId = pSource->i_getFirstMachineBackrefSnapshotId();
3164 if (pSnapshotId)
3165 replaceSnapshotId = *pSnapshotId;
3166
3167 if (replaceMachineId.isValid() && !replaceMachineId.isZero())
3168 {
3169 // Adjust the backreferences, otherwise merging will assert.
3170 // Note that the medium attachment object stays associated
3171 // with the snapshot until the merge was successful.
3172 HRESULT hrc2 = pSource->i_removeBackReference(replaceMachineId, replaceSnapshotId);
3173 AssertComRC(hrc2);
3174
3175 toDelete.push_back(MediumDeleteRec(pHD, pSource, pTarget,
3176 pOnlineMediumAttachment,
3177 fMergeForward,
3178 pParentForTarget,
3179 pChildrenToReparent,
3180 fNeedsOnlineMerge,
3181 pMediumLockList,
3182 pHDLockToken,
3183 replaceMachineId,
3184 replaceSnapshotId));
3185 }
3186 else
3187 toDelete.push_back(MediumDeleteRec(pHD, pSource, pTarget,
3188 pOnlineMediumAttachment,
3189 fMergeForward,
3190 pParentForTarget,
3191 pChildrenToReparent,
3192 fNeedsOnlineMerge,
3193 pMediumLockList,
3194 pHDLockToken));
3195 }
3196
3197 {
3198 /* check available space on the storage */
3199 RTFOFF pcbTotal = 0;
3200 RTFOFF pcbFree = 0;
3201 uint32_t pcbBlock = 0;
3202 uint32_t pcbSector = 0;
3203 std::multimap<uint32_t, uint64_t> neededStorageFreeSpace;
3204 std::map<uint32_t, const char*> serialMapToStoragePath;
3205
3206 for (MediumDeleteRecList::const_iterator
3207 it = toDelete.begin();
3208 it != toDelete.end();
3209 ++it)
3210 {
3211 uint64_t diskSize = 0;
3212 uint32_t pu32Serial = 0;
3213 ComObjPtr<Medium> pSource_local = it->mpSource;
3214 ComObjPtr<Medium> pTarget_local = it->mpTarget;
3215 ComPtr<IMediumFormat> pTargetFormat;
3216
3217 {
3218 if ( pSource_local.isNull()
3219 || pSource_local == pTarget_local)
3220 continue;
3221 }
3222
3223 hrc = pTarget_local->COMGETTER(MediumFormat)(pTargetFormat.asOutParam());
3224 if (FAILED(hrc))
3225 throw hrc;
3226
3227 if (pTarget_local->i_isMediumFormatFile())
3228 {
3229 int vrc = RTFsQuerySerial(pTarget_local->i_getLocationFull().c_str(), &pu32Serial);
3230 if (RT_FAILURE(vrc))
3231 throw setError(E_FAIL,
3232 tr("Unable to merge storage '%s'. Can't get storage UID"),
3233 pTarget_local->i_getLocationFull().c_str());
3234
3235 pSource_local->COMGETTER(Size)((LONG64*)&diskSize);
3236
3237 /** @todo r=klaus this is too pessimistic... should take
3238 * the current size and maximum size of the target image
3239 * into account, because a X GB image with Y GB capacity
3240 * can only grow by Y-X GB (ignoring overhead, which
3241 * unfortunately is hard to estimate, some have next to
3242 * nothing, some have a certain percentage...) */
3243 /* store needed free space in multimap */
3244 neededStorageFreeSpace.insert(std::make_pair(pu32Serial, diskSize));
3245 /* linking storage UID with snapshot path, it is a helper container (just for easy finding needed path) */
3246 serialMapToStoragePath.insert(std::make_pair(pu32Serial, pTarget_local->i_getLocationFull().c_str()));
3247 }
3248 }
3249
3250 while (!neededStorageFreeSpace.empty())
3251 {
3252 std::pair<std::multimap<uint32_t,uint64_t>::iterator,std::multimap<uint32_t,uint64_t>::iterator> ret;
3253 uint64_t commonSourceStoragesSize = 0;
3254
3255 /* find all records in multimap with identical storage UID */
3256 ret = neededStorageFreeSpace.equal_range(neededStorageFreeSpace.begin()->first);
3257 std::multimap<uint32_t,uint64_t>::const_iterator it_ns = ret.first;
3258
3259 for (; it_ns != ret.second ; ++it_ns)
3260 {
3261 commonSourceStoragesSize += it_ns->second;
3262 }
3263
3264 /* find appropriate path by storage UID */
3265 std::map<uint32_t,const char*>::const_iterator it_sm = serialMapToStoragePath.find(ret.first->first);
3266 /* get info about a storage */
3267 if (it_sm == serialMapToStoragePath.end())
3268 {
3269 LogFlowThisFunc(("Path to the storage wasn't found...\n"));
3270
3271 throw setError(E_INVALIDARG,
3272 tr("Unable to merge storage '%s'. Path to the storage wasn't found"),
3273 it_sm->second);
3274 }
3275
3276 int vrc = RTFsQuerySizes(it_sm->second, &pcbTotal, &pcbFree, &pcbBlock, &pcbSector);
3277 if (RT_FAILURE(vrc))
3278 {
3279 throw setError(E_FAIL,
3280 tr("Unable to merge storage '%s'. Can't get the storage size"),
3281 it_sm->second);
3282 }
3283
3284 if (commonSourceStoragesSize > (uint64_t)pcbFree)
3285 {
3286 LogFlowThisFunc(("Not enough free space to merge...\n"));
3287
3288 throw setError(E_OUTOFMEMORY,
3289 tr("Unable to merge storage '%s'. Not enough free storage space"),
3290 it_sm->second);
3291 }
3292
3293 neededStorageFreeSpace.erase(ret.first, ret.second);
3294 }
3295
3296 serialMapToStoragePath.clear();
3297 }
3298
3299 // we can release the locks now since the machine state is MachineState_DeletingSnapshot
3300 treeLock.release();
3301 multiLock.release();
3302
3303 /* Now we checked that we can successfully merge all normal hard disks
3304 * (unless a runtime error like end-of-disc happens). Now get rid of
3305 * the saved state (if present), as that will free some disk space.
3306 * The snapshot itself will be deleted as late as possible, so that
3307 * the user can repeat the delete operation if he runs out of disk
3308 * space or cancels the delete operation. */
3309
3310 /* second pass: */
3311 LogFlowThisFunc(("2: Deleting saved state...\n"));
3312
3313 {
3314 // saveAllSnapshots() needs a machine lock, and the snapshots
3315 // tree is protected by the machine lock as well
3316 AutoWriteLock machineLock(this COMMA_LOCKVAL_SRC_POS);
3317
3318 Utf8Str stateFilePath = task.m_pSnapshot->i_getStateFilePath();
3319 if (!stateFilePath.isEmpty())
3320 {
3321 task.m_pProgress->SetNextOperation(Bstr(tr("Deleting the execution state")).raw(),
3322 1); // weight
3323
3324 i_releaseSavedStateFile(stateFilePath, task.m_pSnapshot /* pSnapshotToIgnore */);
3325
3326 // machine will need saving now
3327 machineLock.release();
3328 mParent->i_markRegistryModified(i_getId());
3329 }
3330 }
3331
3332 /* third pass: */
3333 LogFlowThisFunc(("3: Performing actual hard disk merging...\n"));
3334
3335 /// @todo NEWMEDIA turn the following errors into warnings because the
3336 /// snapshot itself has been already deleted (and interpret these
3337 /// warnings properly on the GUI side)
3338 for (MediumDeleteRecList::iterator it = toDelete.begin();
3339 it != toDelete.end();)
3340 {
3341 const ComObjPtr<Medium> &pMedium(it->mpHD);
3342 ULONG ulWeight;
3343
3344 {
3345 AutoReadLock alock(pMedium COMMA_LOCKVAL_SRC_POS);
3346 ulWeight = (ULONG)(pMedium->i_getSize() / _1M);
3347 }
3348
3349 const char *pszOperationText = it->mfNeedsOnlineMerge ?
3350 tr("Merging differencing image '%s'")
3351 : tr("Resizing before merge differencing image '%s'");
3352
3353 task.m_pProgress->SetNextOperation(BstrFmt(pszOperationText,
3354 pMedium->i_getName().c_str()).raw(),
3355 ulWeight);
3356
3357 bool fNeedSourceUninit = false;
3358 bool fReparentTarget = false;
3359 if (it->mpMediumLockList == NULL)
3360 {
3361 /* no real merge needed, just updating state and delete
3362 * diff files if necessary */
3363 AutoMultiWriteLock2 mLock(&mParent->i_getMediaTreeLockHandle(), pMedium->lockHandle() COMMA_LOCKVAL_SRC_POS);
3364
3365 Assert( !it->mfMergeForward
3366 || pMedium->i_getChildren().size() == 0);
3367
3368 /* Delete the differencing hard disk (has no children). Two
3369 * exceptions: if it's the last medium in the chain or if it's
3370 * a backward merge we don't want to handle due to complexity.
3371 * In both cases leave the image in place. If it's the first
3372 * exception the user can delete it later if he wants. */
3373 if (!pMedium->i_getParent().isNull())
3374 {
3375 Assert(pMedium->i_getState() == MediumState_Deleting);
3376 /* No need to hold the lock any longer. */
3377 mLock.release();
3378 ComObjPtr<Medium> pParent = pMedium->i_getParent();
3379 Guid uMedium = pMedium->i_getId();
3380 DeviceType_T uMediumType = pMedium->i_getDeviceType();
3381 hrc = pMedium->i_deleteStorage(&task.m_pProgress, true /* aWait */, false /* aNotify */);
3382 if (FAILED(hrc))
3383 throw hrc;
3384
3385 pMediaForNotify.insert(pParent);
3386 uIdsForNotify[uMedium] = uMediumType;
3387
3388 // need to uninit the deleted medium
3389 fNeedSourceUninit = true;
3390 }
3391 }
3392 else
3393 {
3394 {
3395 //store ids before merging for notify
3396 pMediaForNotify.insert(it->mpTarget);
3397 if (it->mfMergeForward)
3398 pMediaForNotify.insert(it->mpSource->i_getParent());
3399 else
3400 {
3401 //children which will be reparented to target
3402 for (MediaList::const_iterator iit = it->mpSource->i_getChildren().begin();
3403 iit != it->mpSource->i_getChildren().end();
3404 ++iit)
3405 {
3406 pMediaForNotify.insert(*iit);
3407 }
3408 }
3409 if (it->mfMergeForward)
3410 {
3411 for (ComObjPtr<Medium> pTmpMedium = it->mpTarget->i_getParent();
3412 pTmpMedium && pTmpMedium != it->mpSource;
3413 pTmpMedium = pTmpMedium->i_getParent())
3414 {
3415 uIdsForNotify[pTmpMedium->i_getId()] = pTmpMedium->i_getDeviceType();
3416 }
3417 uIdsForNotify[it->mpSource->i_getId()] = it->mpSource->i_getDeviceType();
3418 }
3419 else
3420 {
3421 for (ComObjPtr<Medium> pTmpMedium = it->mpSource;
3422 pTmpMedium && pTmpMedium != it->mpTarget;
3423 pTmpMedium = pTmpMedium->i_getParent())
3424 {
3425 uIdsForNotify[pTmpMedium->i_getId()] = pTmpMedium->i_getDeviceType();
3426 }
3427 }
3428 }
3429
3430 bool fNeedsSave = false;
3431 if (it->mfNeedsOnlineMerge)
3432 {
3433 // Put the medium merge information (MediumDeleteRec) where
3434 // SessionMachine::FinishOnlineMergeMedium can get at it.
3435 // This callback will arrive while onlineMergeMedium is
3436 // still executing, and there can't be two tasks.
3437 /// @todo r=klaus this hack needs to go, and the logic needs to be "unconvoluted", putting SessionMachine in charge of coordinating the reconfig/resume.
3438 mConsoleTaskData.mDeleteSnapshotInfo = (void *)&(*it);
3439 // online medium merge, in the direction decided earlier
3440 hrc = i_onlineMergeMedium(it->mpOnlineMediumAttachment,
3441 it->mpSource,
3442 it->mpTarget,
3443 it->mfMergeForward,
3444 it->mpParentForTarget,
3445 it->mpChildrenToReparent,
3446 it->mpMediumLockList,
3447 task.m_pProgress,
3448 &fNeedsSave);
3449 mConsoleTaskData.mDeleteSnapshotInfo = NULL;
3450 }
3451 else
3452 {
3453 // normal medium merge, in the direction decided earlier
3454 hrc = it->mpSource->i_mergeTo(it->mpTarget,
3455 it->mfMergeForward,
3456 it->mpParentForTarget,
3457 it->mpChildrenToReparent,
3458 it->mpMediumLockList,
3459 &task.m_pProgress,
3460 true /* aWait */,
3461 false /* aNotify */);
3462 }
3463
3464 // If the merge failed, we need to do our best to have a usable
3465 // VM configuration afterwards. The return code doesn't tell
3466 // whether the merge completed and so we have to check if the
3467 // source medium (diff images are always file based at the
3468 // moment) is still there or not. Be careful not to lose the
3469 // error code below, before the "Delayed failure exit".
3470 if (FAILED(hrc))
3471 {
3472 AutoReadLock mlock(it->mpSource COMMA_LOCKVAL_SRC_POS);
3473 if (!it->mpSource->i_isMediumFormatFile())
3474 // Diff medium not backed by a file - cannot get status so
3475 // be pessimistic.
3476 throw hrc;
3477 const Utf8Str &loc = it->mpSource->i_getLocationFull();
3478 // Source medium is still there, so merge failed early.
3479 if (RTFileExists(loc.c_str()))
3480 throw hrc;
3481
3482 // Source medium is gone. Assume the merge succeeded and
3483 // thus it's safe to remove the attachment. We use the
3484 // "Delayed failure exit" below.
3485 }
3486
3487 // need to change the medium attachment for backward merges
3488 fReparentTarget = !it->mfMergeForward;
3489
3490 if (!it->mfNeedsOnlineMerge)
3491 {
3492 // need to uninit the medium deleted by the merge
3493 fNeedSourceUninit = true;
3494
3495 // delete the no longer needed medium lock list, which
3496 // implicitly handled the unlocking
3497 delete it->mpMediumLockList;
3498 it->mpMediumLockList = NULL;
3499 }
3500 }
3501
3502 // Now that the medium is successfully merged/deleted/whatever,
3503 // remove the medium attachment from the snapshot. For a backwards
3504 // merge the target attachment needs to be removed from the
3505 // snapshot, as the VM will take it over. For forward merges the
3506 // source medium attachment needs to be removed.
3507 ComObjPtr<MediumAttachment> pAtt;
3508 if (fReparentTarget)
3509 {
3510 pAtt = i_findAttachment(*(pSnapMachine->mMediumAttachments.data()),
3511 it->mpTarget);
3512 it->mpTarget->i_removeBackReference(machineId, snapshotId);
3513 }
3514 else
3515 pAtt = i_findAttachment(*(pSnapMachine->mMediumAttachments.data()),
3516 it->mpSource);
3517 pSnapMachine->mMediumAttachments->remove(pAtt);
3518
3519 if (fReparentTarget)
3520 {
3521 // Search for old source attachment and replace with target.
3522 // There can be only one child snapshot in this case.
3523 ComObjPtr<Machine> pMachine = this;
3524 Guid childSnapshotId;
3525 ComObjPtr<Snapshot> pChildSnapshot = task.m_pSnapshot->i_getFirstChild();
3526 if (pChildSnapshot)
3527 {
3528 pMachine = pChildSnapshot->i_getSnapshotMachine();
3529 childSnapshotId = pChildSnapshot->i_getId();
3530 }
3531 pAtt = i_findAttachment(*(pMachine->mMediumAttachments).data(), it->mpSource);
3532 if (pAtt)
3533 {
3534 AutoWriteLock attLock(pAtt COMMA_LOCKVAL_SRC_POS);
3535 pAtt->i_updateMedium(it->mpTarget);
3536 it->mpTarget->i_addBackReference(pMachine->mData->mUuid, childSnapshotId);
3537 }
3538 else
3539 {
3540 // If no attachment is found do not change anything. Maybe
3541 // the source medium was not attached to the snapshot.
3542 // If this is an online deletion the attachment was updated
3543 // already to allow the VM continue execution immediately.
3544 // Needs a bit of special treatment due to this difference.
3545 if (it->mfNeedsOnlineMerge)
3546 it->mpTarget->i_addBackReference(pMachine->mData->mUuid, childSnapshotId);
3547 }
3548 }
3549
3550 if (fNeedSourceUninit)
3551 {
3552 // make sure that the diff image to be deleted has no parent,
3553 // even in error cases (where the deparenting may be missing)
3554 if (it->mpSource->i_getParent())
3555 it->mpSource->i_deparent();
3556 it->mpSource->uninit();
3557 }
3558
3559 // One attachment is merged, must save the settings
3560 mParent->i_markRegistryModified(i_getId());
3561
3562 // prevent calling cancelDeleteSnapshotMedium() for this attachment
3563 it = toDelete.erase(it);
3564
3565 // Delayed failure exit when the merge cleanup failed but the
3566 // merge actually succeeded.
3567 if (FAILED(hrc))
3568 throw hrc;
3569 }
3570
3571 /* 3a: delete NVRAM file if present. */
3572 {
3573 Utf8Str NVRAMPath = pSnapMachine->mNvramStore->i_getNonVolatileStorageFile();
3574 if (NVRAMPath.isNotEmpty() && RTFileExists(NVRAMPath.c_str()))
3575 RTFileDelete(NVRAMPath.c_str());
3576 }
3577
3578 /* third pass: */
3579 {
3580 // beginSnapshotDelete() needs the machine lock, and the snapshots
3581 // tree is protected by the machine lock as well
3582 AutoWriteLock machineLock(this COMMA_LOCKVAL_SRC_POS);
3583
3584 task.m_pSnapshot->i_beginSnapshotDelete();
3585 task.m_pSnapshot->uninit();
3586
3587 machineLock.release();
3588 mParent->i_markRegistryModified(i_getId());
3589 }
3590 }
3591 catch (HRESULT hrcXcpt)
3592 {
3593 mrc = hrcXcpt;
3594 }
3595
3596 if (FAILED(mrc))
3597 {
3598 // preserve existing error info so that the result can
3599 // be properly reported to the progress object below
3600 ErrorInfoKeeper eik;
3601
3602 AutoMultiWriteLock2 multiLock(this->lockHandle(), // machine
3603 &mParent->i_getMediaTreeLockHandle() // media tree
3604 COMMA_LOCKVAL_SRC_POS);
3605
3606 // un-prepare the remaining hard disks
3607 for (MediumDeleteRecList::const_iterator it = toDelete.begin();
3608 it != toDelete.end();
3609 ++it)
3610 i_cancelDeleteSnapshotMedium(it->mpHD, it->mpSource,
3611 it->mpChildrenToReparent,
3612 it->mfNeedsOnlineMerge,
3613 it->mpMediumLockList, it->mpHDLockToken,
3614 it->mMachineId, it->mSnapshotId);
3615 }
3616
3617 // whether we were successful or not, we need to set the machine
3618 // state and save the machine settings;
3619 {
3620 // preserve existing error info so that the result can
3621 // be properly reported to the progress object below
3622 ErrorInfoKeeper eik;
3623
3624 // restore the machine state that was saved when the
3625 // task was started
3626 i_setMachineState(task.m_machineStateBackup);
3627 if (Global::IsOnline(mData->mMachineState))
3628 i_updateMachineStateOnClient();
3629
3630 mParent->i_saveModifiedRegistries();
3631 }
3632
3633 // report the result (this will try to fetch current error info on failure)
3634 task.m_pProgress->i_notifyComplete(mrc);
3635
3636 if (SUCCEEDED(mrc))
3637 {
3638 mParent->i_onSnapshotDeleted(mData->mUuid, snapshotId);
3639 for (std::map<Guid, DeviceType_T>::const_iterator it = uIdsForNotify.begin();
3640 it != uIdsForNotify.end();
3641 ++it)
3642 {
3643 mParent->i_onMediumRegistered(it->first, it->second, FALSE);
3644 }
3645 for (std::set<ComObjPtr<Medium> >::const_iterator it = pMediaForNotify.begin();
3646 it != pMediaForNotify.end();
3647 ++it)
3648 {
3649 if (it->isNotNull())
3650 mParent->i_onMediumConfigChanged(*it);
3651 }
3652 }
3653
3654 LogFlowThisFunc(("Done deleting snapshot (mrc=%08X)\n", (HRESULT)mrc));
3655 LogFlowThisFuncLeave();
3656}
3657
3658/**
3659 * Checks that this hard disk (part of a snapshot) may be deleted/merged and
3660 * performs necessary state changes. Must not be called for writethrough disks
3661 * because there is nothing to delete/merge then.
3662 *
3663 * This method is to be called prior to calling #deleteSnapshotMedium().
3664 * If #deleteSnapshotMedium() is not called or fails, the state modifications
3665 * performed by this method must be undone by #cancelDeleteSnapshotMedium().
3666 *
3667 * @return COM status code
3668 * @param aHD Hard disk which is connected to the snapshot.
3669 * @param aMachineId UUID of machine this hard disk is attached to.
3670 * @param aSnapshotId UUID of snapshot this hard disk is attached to. May
3671 * be a zero UUID if no snapshot is applicable.
3672 * @param fOnlineMergePossible Flag whether an online merge is possible.
3673 * @param aVMMALockList Medium lock list for the medium attachment of this VM.
3674 * Only used if @a fOnlineMergePossible is @c true, and
3675 * must be non-NULL in this case.
3676 * @param aSource Source hard disk for merge (out).
3677 * @param aTarget Target hard disk for merge (out).
3678 * @param aMergeForward Merge direction decision (out).
3679 * @param aParentForTarget New parent if target needs to be reparented (out).
3680 * @param aChildrenToReparent MediumLockList with children which have to be
3681 * reparented to the target (out).
3682 * @param fNeedsOnlineMerge Whether this merge needs to be done online (out).
3683 * If this is set to @a true then the @a aVMMALockList
3684 * parameter has been modified and is returned as
3685 * @a aMediumLockList.
3686 * @param aMediumLockList Where to store the created medium lock list (may
3687 * return NULL if no real merge is necessary).
3688 * @param aHDLockToken Where to store the write lock token for aHD, in case
3689 * it is not merged or deleted (out).
3690 *
3691 * @note Caller must hold media tree lock for writing. This locks this object
3692 * and every medium object on the merge chain for writing.
3693 */
3694HRESULT SessionMachine::i_prepareDeleteSnapshotMedium(const ComObjPtr<Medium> &aHD,
3695 const Guid &aMachineId,
3696 const Guid &aSnapshotId,
3697 bool fOnlineMergePossible,
3698 MediumLockList *aVMMALockList,
3699 ComObjPtr<Medium> &aSource,
3700 ComObjPtr<Medium> &aTarget,
3701 bool &aMergeForward,
3702 ComObjPtr<Medium> &aParentForTarget,
3703 MediumLockList * &aChildrenToReparent,
3704 bool &fNeedsOnlineMerge,
3705 MediumLockList * &aMediumLockList,
3706 ComPtr<IToken> &aHDLockToken)
3707{
3708 Assert(!mParent->i_getMediaTreeLockHandle().isWriteLockOnCurrentThread());
3709 Assert(!fOnlineMergePossible || RT_VALID_PTR(aVMMALockList));
3710
3711 AutoWriteLock alock(aHD COMMA_LOCKVAL_SRC_POS);
3712
3713 // Medium must not be writethrough/shareable/readonly at this point
3714 MediumType_T type = aHD->i_getType();
3715 AssertReturn( type != MediumType_Writethrough
3716 && type != MediumType_Shareable
3717 && type != MediumType_Readonly, E_FAIL);
3718
3719 aChildrenToReparent = NULL;
3720 aMediumLockList = NULL;
3721 fNeedsOnlineMerge = false;
3722
3723 if (aHD->i_getChildren().size() == 0)
3724 {
3725 /* This technically is no merge, set those values nevertheless.
3726 * Helps with updating the medium attachments. */
3727 aSource = aHD;
3728 aTarget = aHD;
3729
3730 /* special treatment of the last hard disk in the chain: */
3731 if (aHD->i_getParent().isNull())
3732 {
3733 /* lock only, to prevent any usage until the snapshot deletion
3734 * is completed */
3735 alock.release();
3736 return aHD->LockWrite(aHDLockToken.asOutParam());
3737 }
3738
3739 /* the differencing hard disk w/o children will be deleted, protect it
3740 * from attaching to other VMs (this is why Deleting) */
3741 return aHD->i_markForDeletion();
3742 }
3743
3744 /* not going multi-merge as it's too expensive */
3745 if (aHD->i_getChildren().size() > 1)
3746 return setError(E_FAIL,
3747 tr("Hard disk '%s' has more than one child hard disk (%d)"),
3748 aHD->i_getLocationFull().c_str(),
3749 aHD->i_getChildren().size());
3750
3751 ComObjPtr<Medium> pChild = aHD->i_getChildren().front();
3752
3753 AutoWriteLock childLock(pChild COMMA_LOCKVAL_SRC_POS);
3754
3755 /* the rest is a normal merge setup */
3756 if (aHD->i_getParent().isNull())
3757 {
3758 /* base hard disk, backward merge */
3759 const Guid *pMachineId1 = pChild->i_getFirstMachineBackrefId();
3760 const Guid *pMachineId2 = aHD->i_getFirstMachineBackrefId();
3761 if (pMachineId1 && pMachineId2 && *pMachineId1 != *pMachineId2)
3762 {
3763 /* backward merge is too tricky, we'll just detach on snapshot
3764 * deletion, so lock only, to prevent any usage */
3765 childLock.release();
3766 alock.release();
3767 return aHD->LockWrite(aHDLockToken.asOutParam());
3768 }
3769
3770 aSource = pChild;
3771 aTarget = aHD;
3772 }
3773 else
3774 {
3775 /* Determine best merge direction. */
3776 bool fMergeForward = true;
3777
3778 childLock.release();
3779 alock.release();
3780 HRESULT hrc = aHD->i_queryPreferredMergeDirection(pChild, fMergeForward);
3781 alock.acquire();
3782 childLock.acquire();
3783
3784 if (FAILED(hrc) && hrc != E_FAIL)
3785 return hrc;
3786
3787 if (fMergeForward)
3788 {
3789 aSource = aHD;
3790 aTarget = pChild;
3791 LogFlowThisFunc(("Forward merging selected\n"));
3792 }
3793 else
3794 {
3795 aSource = pChild;
3796 aTarget = aHD;
3797 LogFlowThisFunc(("Backward merging selected\n"));
3798 }
3799 }
3800
3801 HRESULT hrc;
3802 childLock.release();
3803 alock.release();
3804 hrc = aSource->i_prepareMergeTo(aTarget, &aMachineId, &aSnapshotId,
3805 !fOnlineMergePossible /* fLockMedia */,
3806 aMergeForward, aParentForTarget,
3807 aChildrenToReparent, aMediumLockList);
3808 alock.acquire();
3809 childLock.acquire();
3810 if (SUCCEEDED(hrc) && fOnlineMergePossible)
3811 {
3812 /* Try to lock the newly constructed medium lock list. If it succeeds
3813 * this can be handled as an offline merge, i.e. without the need of
3814 * asking the VM to do the merging. Only continue with the online
3815 * merging preparation if applicable. */
3816 childLock.release();
3817 alock.release();
3818 hrc = aMediumLockList->Lock();
3819 alock.acquire();
3820 childLock.acquire();
3821 if (FAILED(hrc))
3822 {
3823 /* Locking failed, this cannot be done as an offline merge. Try to
3824 * combine the locking information into the lock list of the medium
3825 * attachment in the running VM. If that fails or locking the
3826 * resulting lock list fails then the merge cannot be done online.
3827 * It can be repeated by the user when the VM is shut down. */
3828 MediumLockList::Base::iterator lockListVMMABegin =
3829 aVMMALockList->GetBegin();
3830 MediumLockList::Base::iterator lockListVMMAEnd =
3831 aVMMALockList->GetEnd();
3832 MediumLockList::Base::iterator lockListBegin =
3833 aMediumLockList->GetBegin();
3834 MediumLockList::Base::iterator lockListEnd =
3835 aMediumLockList->GetEnd();
3836 for (MediumLockList::Base::iterator it = lockListVMMABegin,
3837 it2 = lockListBegin;
3838 it2 != lockListEnd;
3839 ++it, ++it2)
3840 {
3841 if ( it == lockListVMMAEnd
3842 || it->GetMedium() != it2->GetMedium())
3843 {
3844 fOnlineMergePossible = false;
3845 break;
3846 }
3847 bool fLockReq = (it2->GetLockRequest() || it->GetLockRequest());
3848 childLock.release();
3849 alock.release();
3850 hrc = it->UpdateLock(fLockReq);
3851 alock.acquire();
3852 childLock.acquire();
3853 if (FAILED(hrc))
3854 {
3855 // could not update the lock, trigger cleanup below
3856 fOnlineMergePossible = false;
3857 break;
3858 }
3859 }
3860
3861 if (fOnlineMergePossible)
3862 {
3863 /* we will lock the children of the source for reparenting */
3864 if (aChildrenToReparent && !aChildrenToReparent->IsEmpty())
3865 {
3866 /* Cannot just call aChildrenToReparent->Lock(), as one of
3867 * the children is the one under which the current state of
3868 * the VM is located, and this means it is already locked
3869 * (for reading). Note that no special unlocking is needed,
3870 * because cancelMergeTo will unlock everything locked in
3871 * its context (using the unlock on destruction), and both
3872 * cancelDeleteSnapshotMedium (in case something fails) and
3873 * FinishOnlineMergeMedium re-define the read/write lock
3874 * state of everything which the VM need, search for the
3875 * UpdateLock method calls. */
3876 childLock.release();
3877 alock.release();
3878 hrc = aChildrenToReparent->Lock(true /* fSkipOverLockedMedia */);
3879 alock.acquire();
3880 childLock.acquire();
3881 MediumLockList::Base::iterator childrenToReparentBegin = aChildrenToReparent->GetBegin();
3882 MediumLockList::Base::iterator childrenToReparentEnd = aChildrenToReparent->GetEnd();
3883 for (MediumLockList::Base::iterator it = childrenToReparentBegin;
3884 it != childrenToReparentEnd;
3885 ++it)
3886 {
3887 ComObjPtr<Medium> pMedium = it->GetMedium();
3888 AutoReadLock mediumLock(pMedium COMMA_LOCKVAL_SRC_POS);
3889 if (!it->IsLocked())
3890 {
3891 mediumLock.release();
3892 childLock.release();
3893 alock.release();
3894 hrc = aVMMALockList->Update(pMedium, true);
3895 alock.acquire();
3896 childLock.acquire();
3897 mediumLock.acquire();
3898 if (FAILED(hrc))
3899 throw hrc;
3900 }
3901 }
3902 }
3903 }
3904
3905 if (fOnlineMergePossible)
3906 {
3907 childLock.release();
3908 alock.release();
3909 hrc = aVMMALockList->Lock();
3910 alock.acquire();
3911 childLock.acquire();
3912 if (FAILED(hrc))
3913 {
3914 aSource->i_cancelMergeTo(aChildrenToReparent, aMediumLockList);
3915 hrc = setError(hrc,
3916 tr("Cannot lock hard disk '%s' for a live merge"),
3917 aHD->i_getLocationFull().c_str());
3918 }
3919 else
3920 {
3921 delete aMediumLockList;
3922 aMediumLockList = aVMMALockList;
3923 fNeedsOnlineMerge = true;
3924 }
3925 }
3926 else
3927 {
3928 aSource->i_cancelMergeTo(aChildrenToReparent, aMediumLockList);
3929 hrc = setError(hrc,
3930 tr("Failed to construct lock list for a live merge of hard disk '%s'"),
3931 aHD->i_getLocationFull().c_str());
3932 }
3933
3934 // fix the VM's lock list if anything failed
3935 if (FAILED(hrc))
3936 {
3937 lockListVMMABegin = aVMMALockList->GetBegin();
3938 lockListVMMAEnd = aVMMALockList->GetEnd();
3939 MediumLockList::Base::iterator lockListLast = lockListVMMAEnd;
3940 --lockListLast;
3941 for (MediumLockList::Base::iterator it = lockListVMMABegin;
3942 it != lockListVMMAEnd;
3943 ++it)
3944 {
3945 childLock.release();
3946 alock.release();
3947 it->UpdateLock(it == lockListLast);
3948 alock.acquire();
3949 childLock.acquire();
3950 ComObjPtr<Medium> pMedium = it->GetMedium();
3951 AutoWriteLock mediumLock(pMedium COMMA_LOCKVAL_SRC_POS);
3952 // blindly apply this, only needed for medium objects which
3953 // would be deleted as part of the merge
3954 pMedium->i_unmarkLockedForDeletion();
3955 }
3956 }
3957 }
3958 }
3959 else if (FAILED(hrc))
3960 {
3961 aSource->i_cancelMergeTo(aChildrenToReparent, aMediumLockList);
3962 hrc = setError(hrc,
3963 tr("Cannot lock hard disk '%s' when deleting a snapshot"),
3964 aHD->i_getLocationFull().c_str());
3965 }
3966
3967 return hrc;
3968}
3969
3970/**
3971 * Cancels the deletion/merging of this hard disk (part of a snapshot). Undoes
3972 * what #prepareDeleteSnapshotMedium() did. Must be called if
3973 * #deleteSnapshotMedium() is not called or fails.
3974 *
3975 * @param aHD Hard disk which is connected to the snapshot.
3976 * @param aSource Source hard disk for merge.
3977 * @param aChildrenToReparent Children to unlock.
3978 * @param fNeedsOnlineMerge Whether this merge needs to be done online.
3979 * @param aMediumLockList Medium locks to cancel.
3980 * @param aHDLockToken Optional write lock token for aHD.
3981 * @param aMachineId Machine id to attach the medium to.
3982 * @param aSnapshotId Snapshot id to attach the medium to.
3983 *
3984 * @note Locks the medium tree and the hard disks in the chain for writing.
3985 */
3986void SessionMachine::i_cancelDeleteSnapshotMedium(const ComObjPtr<Medium> &aHD,
3987 const ComObjPtr<Medium> &aSource,
3988 MediumLockList *aChildrenToReparent,
3989 bool fNeedsOnlineMerge,
3990 MediumLockList *aMediumLockList,
3991 const ComPtr<IToken> &aHDLockToken,
3992 const Guid &aMachineId,
3993 const Guid &aSnapshotId)
3994{
3995 if (aMediumLockList == NULL)
3996 {
3997 AutoMultiWriteLock2 mLock(&mParent->i_getMediaTreeLockHandle(), aHD->lockHandle() COMMA_LOCKVAL_SRC_POS);
3998
3999 Assert(aHD->i_getChildren().size() == 0);
4000
4001 if (aHD->i_getParent().isNull())
4002 {
4003 Assert(!aHDLockToken.isNull());
4004 if (!aHDLockToken.isNull())
4005 {
4006 HRESULT hrc = aHDLockToken->Abandon();
4007 AssertComRC(hrc);
4008 }
4009 }
4010 else
4011 {
4012 HRESULT hrc = aHD->i_unmarkForDeletion();
4013 AssertComRC(hrc);
4014 }
4015 }
4016 else
4017 {
4018 if (fNeedsOnlineMerge)
4019 {
4020 // Online merge uses the medium lock list of the VM, so give
4021 // an empty list to cancelMergeTo so that it works as designed.
4022 aSource->i_cancelMergeTo(aChildrenToReparent, new MediumLockList());
4023
4024 // clean up the VM medium lock list ourselves
4025 MediumLockList::Base::iterator lockListBegin =
4026 aMediumLockList->GetBegin();
4027 MediumLockList::Base::iterator lockListEnd =
4028 aMediumLockList->GetEnd();
4029 MediumLockList::Base::iterator lockListLast = lockListEnd;
4030 --lockListLast;
4031 for (MediumLockList::Base::iterator it = lockListBegin;
4032 it != lockListEnd;
4033 ++it)
4034 {
4035 ComObjPtr<Medium> pMedium = it->GetMedium();
4036 AutoWriteLock mediumLock(pMedium COMMA_LOCKVAL_SRC_POS);
4037 if (pMedium->i_getState() == MediumState_Deleting)
4038 pMedium->i_unmarkForDeletion();
4039 else
4040 {
4041 // blindly apply this, only needed for medium objects which
4042 // would be deleted as part of the merge
4043 pMedium->i_unmarkLockedForDeletion();
4044 }
4045 mediumLock.release();
4046 it->UpdateLock(it == lockListLast);
4047 mediumLock.acquire();
4048 }
4049 }
4050 else
4051 {
4052 aSource->i_cancelMergeTo(aChildrenToReparent, aMediumLockList);
4053 }
4054 }
4055
4056 if (aMachineId.isValid() && !aMachineId.isZero())
4057 {
4058 // reattach the source media to the snapshot
4059 HRESULT hrc = aSource->i_addBackReference(aMachineId, aSnapshotId);
4060 AssertComRC(hrc);
4061 }
4062}
4063
4064/**
4065 * Perform an online merge of a hard disk, i.e. the equivalent of
4066 * Medium::mergeTo(), just for running VMs. If this fails you need to call
4067 * #cancelDeleteSnapshotMedium().
4068 *
4069 * @return COM status code
4070 * @param aMediumAttachment Identify where the disk is attached in the VM.
4071 * @param aSource Source hard disk for merge.
4072 * @param aTarget Target hard disk for merge.
4073 * @param fMergeForward Merge direction.
4074 * @param aParentForTarget New parent if target needs to be reparented.
4075 * @param aChildrenToReparent Medium lock list with children which have to be
4076 * reparented to the target.
4077 * @param aMediumLockList Where to store the created medium lock list (may
4078 * return NULL if no real merge is necessary).
4079 * @param aProgress Progress indicator.
4080 * @param pfNeedsMachineSaveSettings Whether the VM settings need to be saved (out).
4081 */
4082HRESULT SessionMachine::i_onlineMergeMedium(const ComObjPtr<MediumAttachment> &aMediumAttachment,
4083 const ComObjPtr<Medium> &aSource,
4084 const ComObjPtr<Medium> &aTarget,
4085 bool fMergeForward,
4086 const ComObjPtr<Medium> &aParentForTarget,
4087 MediumLockList *aChildrenToReparent,
4088 MediumLockList *aMediumLockList,
4089 ComObjPtr<Progress> &aProgress,
4090 bool *pfNeedsMachineSaveSettings)
4091{
4092 AssertReturn(aSource != NULL, E_FAIL);
4093 AssertReturn(aTarget != NULL, E_FAIL);
4094 AssertReturn(aSource != aTarget, E_FAIL);
4095 AssertReturn(aMediumLockList != NULL, E_FAIL);
4096 NOREF(fMergeForward);
4097 NOREF(aParentForTarget);
4098 NOREF(aChildrenToReparent);
4099
4100 HRESULT hrc = S_OK;
4101
4102 try
4103 {
4104 // Similar code appears in Medium::taskMergeHandle, so
4105 // if you make any changes below check whether they are applicable
4106 // in that context as well.
4107
4108 unsigned uTargetIdx = (unsigned)-1;
4109 unsigned uSourceIdx = (unsigned)-1;
4110 /* Sanity check all hard disks in the chain. */
4111 MediumLockList::Base::iterator lockListBegin =
4112 aMediumLockList->GetBegin();
4113 MediumLockList::Base::iterator lockListEnd =
4114 aMediumLockList->GetEnd();
4115 unsigned i = 0;
4116 for (MediumLockList::Base::iterator it = lockListBegin;
4117 it != lockListEnd;
4118 ++it)
4119 {
4120 MediumLock &mediumLock = *it;
4121 const ComObjPtr<Medium> &pMedium = mediumLock.GetMedium();
4122
4123 if (pMedium == aSource)
4124 uSourceIdx = i;
4125 else if (pMedium == aTarget)
4126 uTargetIdx = i;
4127
4128 // In Medium::taskMergeHandler there is lots of consistency
4129 // checking which we cannot do here, as the state details are
4130 // impossible to get outside the Medium class. The locking should
4131 // have done the checks already.
4132
4133 i++;
4134 }
4135
4136 ComAssertThrow( uSourceIdx != (unsigned)-1
4137 && uTargetIdx != (unsigned)-1, E_FAIL);
4138
4139 ComPtr<IInternalSessionControl> directControl;
4140 {
4141 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
4142
4143 if (mData->mSession.mState != SessionState_Locked)
4144 throw setError(VBOX_E_INVALID_VM_STATE,
4145 tr("Machine is not locked by a session (session state: %s)"),
4146 Global::stringifySessionState(mData->mSession.mState));
4147 directControl = mData->mSession.mDirectControl;
4148 }
4149
4150 // Must not hold any locks here, as this will call back to finish
4151 // updating the medium attachment, chain linking and state.
4152 hrc = directControl->OnlineMergeMedium(aMediumAttachment, uSourceIdx, uTargetIdx, aProgress);
4153 if (FAILED(hrc))
4154 throw hrc;
4155 }
4156 catch (HRESULT hrcXcpt) { hrc = hrcXcpt; }
4157
4158 // The callback mentioned above takes care of update the medium state
4159
4160 if (pfNeedsMachineSaveSettings)
4161 *pfNeedsMachineSaveSettings = true;
4162
4163 return hrc;
4164}
4165
4166/**
4167 * Implementation for IInternalMachineControl::finishOnlineMergeMedium().
4168 *
4169 * Gets called after the successful completion of an online merge from
4170 * Console::onlineMergeMedium(), which gets invoked indirectly above in
4171 * the call to IInternalSessionControl::onlineMergeMedium.
4172 *
4173 * This updates the medium information and medium state so that the VM
4174 * can continue with the updated state of the medium chain.
4175 */
4176HRESULT SessionMachine::finishOnlineMergeMedium()
4177{
4178 HRESULT hrc = S_OK;
4179 MediumDeleteRec *pDeleteRec = (MediumDeleteRec *)mConsoleTaskData.mDeleteSnapshotInfo;
4180 AssertReturn(pDeleteRec, E_FAIL);
4181 bool fSourceHasChildren = false;
4182
4183 // all hard disks but the target were successfully deleted by
4184 // the merge; reparent target if necessary and uninitialize media
4185
4186 AutoWriteLock treeLock(mParent->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
4187
4188 // Declare this here to make sure the object does not get uninitialized
4189 // before this method completes. Would normally happen as halfway through
4190 // we delete the last reference to the no longer existing medium object.
4191 ComObjPtr<Medium> targetChild;
4192
4193 if (pDeleteRec->mfMergeForward)
4194 {
4195 // first, unregister the target since it may become a base
4196 // hard disk which needs re-registration
4197 hrc = mParent->i_unregisterMedium(pDeleteRec->mpTarget);
4198 AssertComRC(hrc);
4199
4200 // then, reparent it and disconnect the deleted branch at
4201 // both ends (chain->parent() is source's parent)
4202 pDeleteRec->mpTarget->i_deparent();
4203 pDeleteRec->mpTarget->i_setParent(pDeleteRec->mpParentForTarget);
4204 if (pDeleteRec->mpParentForTarget)
4205 pDeleteRec->mpSource->i_deparent();
4206
4207 // then, register again
4208 hrc = mParent->i_registerMedium(pDeleteRec->mpTarget, &pDeleteRec->mpTarget, treeLock);
4209 AssertComRC(hrc);
4210 }
4211 else
4212 {
4213 Assert(pDeleteRec->mpTarget->i_getChildren().size() == 1);
4214 targetChild = pDeleteRec->mpTarget->i_getChildren().front();
4215
4216 // disconnect the deleted branch at the elder end
4217 targetChild->i_deparent();
4218
4219 // Update parent UUIDs of the source's children, reparent them and
4220 // disconnect the deleted branch at the younger end
4221 if (pDeleteRec->mpChildrenToReparent && !pDeleteRec->mpChildrenToReparent->IsEmpty())
4222 {
4223 fSourceHasChildren = true;
4224 // Fix the parent UUID of the images which needs to be moved to
4225 // underneath target. The running machine has the images opened,
4226 // but only for reading since the VM is paused. If anything fails
4227 // we must continue. The worst possible result is that the images
4228 // need manual fixing via VBoxManage to adjust the parent UUID.
4229 treeLock.release();
4230 pDeleteRec->mpTarget->i_fixParentUuidOfChildren(pDeleteRec->mpChildrenToReparent);
4231 // The childen are still write locked, unlock them now and don't
4232 // rely on the destructor doing it very late.
4233 pDeleteRec->mpChildrenToReparent->Unlock();
4234 treeLock.acquire();
4235
4236 // obey {parent,child} lock order
4237 AutoWriteLock sourceLock(pDeleteRec->mpSource COMMA_LOCKVAL_SRC_POS);
4238
4239 MediumLockList::Base::iterator childrenBegin = pDeleteRec->mpChildrenToReparent->GetBegin();
4240 MediumLockList::Base::iterator childrenEnd = pDeleteRec->mpChildrenToReparent->GetEnd();
4241 for (MediumLockList::Base::iterator it = childrenBegin;
4242 it != childrenEnd;
4243 ++it)
4244 {
4245 Medium *pMedium = it->GetMedium();
4246 AutoWriteLock childLock(pMedium COMMA_LOCKVAL_SRC_POS);
4247
4248 pMedium->i_deparent(); // removes pMedium from source
4249 pMedium->i_setParent(pDeleteRec->mpTarget);
4250 }
4251 }
4252 }
4253
4254 /* unregister and uninitialize all hard disks removed by the merge */
4255 MediumLockList *pMediumLockList = NULL;
4256 hrc = mData->mSession.mLockedMedia.Get(pDeleteRec->mpOnlineMediumAttachment, pMediumLockList);
4257 const ComObjPtr<Medium> &pLast = pDeleteRec->mfMergeForward ? pDeleteRec->mpTarget : pDeleteRec->mpSource;
4258 AssertReturn(SUCCEEDED(hrc) && pMediumLockList, E_FAIL);
4259 MediumLockList::Base::iterator lockListBegin =
4260 pMediumLockList->GetBegin();
4261 MediumLockList::Base::iterator lockListEnd =
4262 pMediumLockList->GetEnd();
4263 for (MediumLockList::Base::iterator it = lockListBegin;
4264 it != lockListEnd;
4265 )
4266 {
4267 MediumLock &mediumLock = *it;
4268 /* Create a real copy of the medium pointer, as the medium
4269 * lock deletion below would invalidate the referenced object. */
4270 const ComObjPtr<Medium> pMedium = mediumLock.GetMedium();
4271
4272 /* The target and all images not merged (readonly) are skipped */
4273 if ( pMedium == pDeleteRec->mpTarget
4274 || pMedium->i_getState() == MediumState_LockedRead)
4275 {
4276 ++it;
4277 }
4278 else
4279 {
4280 hrc = mParent->i_unregisterMedium(pMedium);
4281 AssertComRC(hrc);
4282
4283 /* now, uninitialize the deleted hard disk (note that
4284 * due to the Deleting state, uninit() will not touch
4285 * the parent-child relationship so we need to
4286 * uninitialize each disk individually) */
4287
4288 /* note that the operation initiator hard disk (which is
4289 * normally also the source hard disk) is a special case
4290 * -- there is one more caller added by Task to it which
4291 * we must release. Also, if we are in sync mode, the
4292 * caller may still hold an AutoCaller instance for it
4293 * and therefore we cannot uninit() it (it's therefore
4294 * the caller's responsibility) */
4295 if (pMedium == pDeleteRec->mpSource)
4296 {
4297 Assert(pDeleteRec->mpSource->i_getChildren().size() == 0);
4298 Assert(pDeleteRec->mpSource->i_getFirstMachineBackrefId() == NULL);
4299 }
4300
4301 /* Delete the medium lock list entry, which also releases the
4302 * caller added by MergeChain before uninit() and updates the
4303 * iterator to point to the right place. */
4304 hrc = pMediumLockList->RemoveByIterator(it);
4305 AssertComRC(hrc);
4306
4307 treeLock.release();
4308 pMedium->uninit();
4309 treeLock.acquire();
4310 }
4311
4312 /* Stop as soon as we reached the last medium affected by the merge.
4313 * The remaining images must be kept unchanged. */
4314 if (pMedium == pLast)
4315 break;
4316 }
4317
4318 /* Could be in principle folded into the previous loop, but let's keep
4319 * things simple. Update the medium locking to be the standard state:
4320 * all parent images locked for reading, just the last diff for writing. */
4321 lockListBegin = pMediumLockList->GetBegin();
4322 lockListEnd = pMediumLockList->GetEnd();
4323 MediumLockList::Base::iterator lockListLast = lockListEnd;
4324 --lockListLast;
4325 for (MediumLockList::Base::iterator it = lockListBegin;
4326 it != lockListEnd;
4327 ++it)
4328 {
4329 it->UpdateLock(it == lockListLast);
4330 }
4331
4332 /* If this is a backwards merge of the only remaining snapshot (i.e. the
4333 * source has no children) then update the medium associated with the
4334 * attachment, as the previously associated one (source) is now deleted.
4335 * Without the immediate update the VM could not continue running. */
4336 if (!pDeleteRec->mfMergeForward && !fSourceHasChildren)
4337 {
4338 AutoWriteLock attLock(pDeleteRec->mpOnlineMediumAttachment COMMA_LOCKVAL_SRC_POS);
4339 pDeleteRec->mpOnlineMediumAttachment->i_updateMedium(pDeleteRec->mpTarget);
4340 }
4341
4342 return S_OK;
4343}
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