VirtualBox

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

Last change on this file since 44406 was 44406, checked in by vboxsync, 12 years ago

Main/Snapshot: fix indentation

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

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