VirtualBox

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

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

Main: renavation com::Guid class. PR5744

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 129.2 KB
Line 
1/* $Id: SnapshotImpl.cpp 44039 2012-12-05 12:08:52Z vboxsync $ */
2/** @file
3 *
4 * COM class implementation for Snapshot and SnapshotMachine in VBoxSVC.
5 */
6
7/*
8 * Copyright (C) 2006-2012 Oracle Corporation
9 *
10 * This file is part of VirtualBox Open Source Edition (OSE), as
11 * available from http://www.215389.xyz. 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 // creating a new online snapshot: then we need a fresh saved state file
1443 composeSavedStateFilename(strStateFilePath);
1444 else if (mData->mMachineState == MachineState_Saved)
1445 // taking an online snapshot from machine in "saved" state: then use existing state file
1446 strStateFilePath = mSSData->strStateFilePath;
1447
1448 if (strStateFilePath.isNotEmpty())
1449 {
1450 // ensure the directory for the saved state file exists
1451 HRESULT rc = VirtualBox::ensureFilePathExists(strStateFilePath, true /* fCreate */);
1452 if (FAILED(rc)) return rc;
1453 }
1454
1455 /* create a snapshot machine object */
1456 ComObjPtr<SnapshotMachine> snapshotMachine;
1457 snapshotMachine.createObject();
1458 HRESULT rc = snapshotMachine->init(this, snapshotId.ref(), strStateFilePath);
1459 AssertComRCReturn(rc, rc);
1460
1461 /* create a snapshot object */
1462 RTTIMESPEC time;
1463 ComObjPtr<Snapshot> pSnapshot;
1464 pSnapshot.createObject();
1465 rc = pSnapshot->init(mParent,
1466 snapshotId,
1467 aName,
1468 aDescription,
1469 *RTTimeNow(&time),
1470 snapshotMachine,
1471 mData->mCurrentSnapshot);
1472 AssertComRCReturnRC(rc);
1473
1474 /* fill in the snapshot data */
1475 mConsoleTaskData.mLastState = mData->mMachineState;
1476 mConsoleTaskData.mSnapshot = pSnapshot;
1477 /// @todo in the long run the progress object should be moved to
1478 // VBoxSVC to avoid trouble with monitoring the progress object state
1479 // when the process where it lives is terminating shortly after the
1480 // operation completed.
1481
1482 try
1483 {
1484 LogFlowThisFunc(("Creating differencing hard disks (online=%d)...\n",
1485 fTakingSnapshotOnline));
1486
1487 // backup the media data so we can recover if things goes wrong along the day;
1488 // the matching commit() is in fixupMedia() during endSnapshot()
1489 setModified(IsModified_Storage);
1490 mMediaData.backup();
1491
1492 /* Console::fntTakeSnapshotWorker and friends expects this. */
1493 if (mConsoleTaskData.mLastState == MachineState_Running)
1494 setMachineState(MachineState_LiveSnapshotting);
1495 else
1496 setMachineState(MachineState_Saving); /** @todo Confusing! Saving is used for both online and offline snapshots. */
1497
1498 alock.release();
1499 /* create new differencing hard disks and attach them to this machine */
1500 rc = createImplicitDiffs(aConsoleProgress,
1501 1, // operation weight; must be the same as in Console::TakeSnapshot()
1502 !!fTakingSnapshotOnline);
1503 if (FAILED(rc))
1504 throw rc;
1505
1506 // if we got this far without an error, then save the media registries
1507 // that got modified for the diff images
1508 mParent->saveModifiedRegistries();
1509 }
1510 catch (HRESULT hrc)
1511 {
1512 LogThisFunc(("Caught %Rhrc [%s]\n", hrc, Global::stringifyMachineState(mData->mMachineState) ));
1513 if ( mConsoleTaskData.mLastState != mData->mMachineState
1514 && ( mConsoleTaskData.mLastState == MachineState_Running
1515 ? mData->mMachineState == MachineState_LiveSnapshotting
1516 : mData->mMachineState == MachineState_Saving)
1517 )
1518 setMachineState(mConsoleTaskData.mLastState);
1519
1520 pSnapshot->uninit();
1521 pSnapshot.setNull();
1522 mConsoleTaskData.mLastState = MachineState_Null;
1523 mConsoleTaskData.mSnapshot.setNull();
1524
1525 rc = hrc;
1526
1527 // @todo r=dj what with the implicit diff that we created above? this is never cleaned up
1528 }
1529
1530 if (fTakingSnapshotOnline && SUCCEEDED(rc))
1531 strStateFilePath.cloneTo(aStateFilePath);
1532 else
1533 *aStateFilePath = NULL;
1534
1535 LogFlowThisFunc(("LEAVE - %Rhrc [%s]\n", rc, Global::stringifyMachineState(mData->mMachineState) ));
1536 return rc;
1537}
1538
1539/**
1540 * Implementation for IInternalMachineControl::endTakingSnapshot().
1541 *
1542 * Called by the Console when it's done saving the VM state into the snapshot
1543 * (if online) and reconfiguring the hard disks. See BeginTakingSnapshot() above.
1544 *
1545 * This also gets called if the console part of snapshotting failed after the
1546 * BeginTakingSnapshot() call, to clean up the server side.
1547 *
1548 * @note Locks VirtualBox and this object for writing.
1549 *
1550 * @param aSuccess Whether Console was successful with the client-side snapshot things.
1551 * @return
1552 */
1553STDMETHODIMP SessionMachine::EndTakingSnapshot(BOOL aSuccess)
1554{
1555 LogFlowThisFunc(("\n"));
1556
1557 AutoCaller autoCaller(this);
1558 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
1559
1560 AutoWriteLock machineLock(this COMMA_LOCKVAL_SRC_POS);
1561
1562 AssertReturn( !aSuccess
1563 || ( ( mData->mMachineState == MachineState_Saving
1564 || mData->mMachineState == MachineState_LiveSnapshotting)
1565 && mConsoleTaskData.mLastState != MachineState_Null
1566 && !mConsoleTaskData.mSnapshot.isNull()
1567 )
1568 , E_FAIL);
1569
1570 /*
1571 * Restore the state we had when BeginTakingSnapshot() was called,
1572 * Console::fntTakeSnapshotWorker restores its local copy when we return.
1573 * If the state was Running, then let Console::fntTakeSnapshotWorker do it
1574 * all to avoid races.
1575 */
1576 if ( mData->mMachineState != mConsoleTaskData.mLastState
1577 && mConsoleTaskData.mLastState != MachineState_Running
1578 )
1579 setMachineState(mConsoleTaskData.mLastState);
1580
1581 ComObjPtr<Snapshot> pOldFirstSnap = mData->mFirstSnapshot;
1582 ComObjPtr<Snapshot> pOldCurrentSnap = mData->mCurrentSnapshot;
1583
1584 bool fOnline = Global::IsOnline(mConsoleTaskData.mLastState);
1585
1586 HRESULT rc = S_OK;
1587
1588 if (aSuccess)
1589 {
1590 // new snapshot becomes the current one
1591 mData->mCurrentSnapshot = mConsoleTaskData.mSnapshot;
1592
1593 /* memorize the first snapshot if necessary */
1594 if (!mData->mFirstSnapshot)
1595 mData->mFirstSnapshot = mData->mCurrentSnapshot;
1596
1597 int flSaveSettings = SaveS_Force; // do not do a deep compare in machine settings,
1598 // snapshots change, so we know we need to save
1599 if (!fOnline)
1600 /* the machine was powered off or saved when taking a snapshot, so
1601 * reset the mCurrentStateModified flag */
1602 flSaveSettings |= SaveS_ResetCurStateModified;
1603
1604 rc = saveSettings(NULL, flSaveSettings);
1605 }
1606
1607 if (aSuccess && SUCCEEDED(rc))
1608 {
1609 /* associate old hard disks with the snapshot and do locking/unlocking*/
1610 commitMedia(fOnline);
1611
1612 /* inform callbacks */
1613 mParent->onSnapshotTaken(mData->mUuid,
1614 mConsoleTaskData.mSnapshot->getId());
1615 machineLock.release();
1616 }
1617 else
1618 {
1619 /* delete all differencing hard disks created (this will also attach
1620 * their parents back by rolling back mMediaData) */
1621 machineLock.release();
1622
1623 rollbackMedia();
1624
1625 mData->mFirstSnapshot = pOldFirstSnap; // might have been changed above
1626 mData->mCurrentSnapshot = pOldCurrentSnap; // might have been changed above
1627
1628 // delete the saved state file (it might have been already created)
1629 if (fOnline)
1630 // no need to test for whether the saved state file is shared: an online
1631 // snapshot means that a new saved state file was created, which we must
1632 // clean up now
1633 RTFileDelete(mConsoleTaskData.mSnapshot->getStateFilePath().c_str());
1634 machineLock.acquire();
1635
1636
1637 mConsoleTaskData.mSnapshot->uninit();
1638 machineLock.release();
1639
1640 }
1641
1642 /* clear out the snapshot data */
1643 mConsoleTaskData.mLastState = MachineState_Null;
1644 mConsoleTaskData.mSnapshot.setNull();
1645
1646 /* machineLock has been released already */
1647
1648 mParent->saveModifiedRegistries();
1649
1650 return rc;
1651}
1652
1653////////////////////////////////////////////////////////////////////////////////
1654//
1655// RestoreSnapshot methods (SessionMachine and related tasks)
1656//
1657////////////////////////////////////////////////////////////////////////////////
1658
1659/**
1660 * Implementation for IInternalMachineControl::restoreSnapshot().
1661 *
1662 * Gets called from Console::RestoreSnapshot(), and that's basically the
1663 * only thing Console does. Restoring a snapshot happens entirely on the
1664 * server side since the machine cannot be running.
1665 *
1666 * This creates a new thread that does the work and returns a progress
1667 * object to the client which is then returned to the caller of
1668 * Console::RestoreSnapshot().
1669 *
1670 * Actual work then takes place in RestoreSnapshotTask::handler().
1671 *
1672 * @note Locks this + children objects for writing!
1673 *
1674 * @param aInitiator in: rhe console on which Console::RestoreSnapshot was called.
1675 * @param aSnapshot in: the snapshot to restore.
1676 * @param aMachineState in: client-side machine state.
1677 * @param aProgress out: progress object to monitor restore thread.
1678 * @return
1679 */
1680STDMETHODIMP SessionMachine::RestoreSnapshot(IConsole *aInitiator,
1681 ISnapshot *aSnapshot,
1682 MachineState_T *aMachineState,
1683 IProgress **aProgress)
1684{
1685 LogFlowThisFuncEnter();
1686
1687 AssertReturn(aInitiator, E_INVALIDARG);
1688 AssertReturn(aSnapshot && aMachineState && aProgress, E_POINTER);
1689
1690 AutoCaller autoCaller(this);
1691 AssertComRCReturn(autoCaller.rc(), autoCaller.rc());
1692
1693 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1694
1695 // machine must not be running
1696 ComAssertRet(!Global::IsOnlineOrTransient(mData->mMachineState),
1697 E_FAIL);
1698
1699 ComObjPtr<Snapshot> pSnapshot(static_cast<Snapshot*>(aSnapshot));
1700 ComObjPtr<SnapshotMachine> pSnapMachine = pSnapshot->getSnapshotMachine();
1701
1702 // create a progress object. The number of operations is:
1703 // 1 (preparing) + # of hard disks + 1 (if we need to copy the saved state file) */
1704 LogFlowThisFunc(("Going thru snapshot machine attachments to determine progress setup\n"));
1705
1706 ULONG ulOpCount = 1; // one for preparations
1707 ULONG ulTotalWeight = 1; // one for preparations
1708 for (MediaData::AttachmentList::iterator it = pSnapMachine->mMediaData->mAttachments.begin();
1709 it != pSnapMachine->mMediaData->mAttachments.end();
1710 ++it)
1711 {
1712 ComObjPtr<MediumAttachment> &pAttach = *it;
1713 AutoReadLock attachLock(pAttach COMMA_LOCKVAL_SRC_POS);
1714 if (pAttach->getType() == DeviceType_HardDisk)
1715 {
1716 ++ulOpCount;
1717 ++ulTotalWeight; // assume one MB weight for each differencing hard disk to manage
1718 Assert(pAttach->getMedium());
1719 LogFlowThisFunc(("op %d: considering hard disk attachment %s\n", ulOpCount, pAttach->getMedium()->getName().c_str()));
1720 }
1721 }
1722
1723 ComObjPtr<Progress> pProgress;
1724 pProgress.createObject();
1725 pProgress->init(mParent, aInitiator,
1726 BstrFmt(tr("Restoring snapshot '%s'"), pSnapshot->getName().c_str()).raw(),
1727 FALSE /* aCancelable */,
1728 ulOpCount,
1729 ulTotalWeight,
1730 Bstr(tr("Restoring machine settings")).raw(),
1731 1);
1732
1733 /* create and start the task on a separate thread (note that it will not
1734 * start working until we release alock) */
1735 RestoreSnapshotTask *task = new RestoreSnapshotTask(this,
1736 pProgress,
1737 pSnapshot);
1738 int vrc = RTThreadCreate(NULL,
1739 taskHandler,
1740 (void*)task,
1741 0,
1742 RTTHREADTYPE_MAIN_WORKER,
1743 0,
1744 "RestoreSnap");
1745 if (RT_FAILURE(vrc))
1746 {
1747 delete task;
1748 ComAssertRCRet(vrc, E_FAIL);
1749 }
1750
1751 /* set the proper machine state (note: after creating a Task instance) */
1752 setMachineState(MachineState_RestoringSnapshot);
1753
1754 /* return the progress to the caller */
1755 pProgress.queryInterfaceTo(aProgress);
1756
1757 /* return the new state to the caller */
1758 *aMachineState = mData->mMachineState;
1759
1760 LogFlowThisFuncLeave();
1761
1762 return S_OK;
1763}
1764
1765/**
1766 * Worker method for the restore snapshot thread created by SessionMachine::RestoreSnapshot().
1767 * This method gets called indirectly through SessionMachine::taskHandler() which then
1768 * calls RestoreSnapshotTask::handler().
1769 *
1770 * The RestoreSnapshotTask contains the progress object returned to the console by
1771 * SessionMachine::RestoreSnapshot, through which progress and results are reported.
1772 *
1773 * @note Locks mParent + this object for writing.
1774 *
1775 * @param aTask Task data.
1776 */
1777void SessionMachine::restoreSnapshotHandler(RestoreSnapshotTask &aTask)
1778{
1779 LogFlowThisFuncEnter();
1780
1781 AutoCaller autoCaller(this);
1782
1783 LogFlowThisFunc(("state=%d\n", autoCaller.state()));
1784 if (!autoCaller.isOk())
1785 {
1786 /* we might have been uninitialized because the session was accidentally
1787 * closed by the client, so don't assert */
1788 aTask.pProgress->notifyComplete(E_FAIL,
1789 COM_IIDOF(IMachine),
1790 getComponentName(),
1791 tr("The session has been accidentally closed"));
1792
1793 LogFlowThisFuncLeave();
1794 return;
1795 }
1796
1797 HRESULT rc = S_OK;
1798
1799 bool stateRestored = false;
1800
1801 try
1802 {
1803 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1804
1805 /* Discard all current changes to mUserData (name, OSType etc.).
1806 * Note that the machine is powered off, so there is no need to inform
1807 * the direct session. */
1808 if (mData->flModifications)
1809 rollback(false /* aNotify */);
1810
1811 /* Delete the saved state file if the machine was Saved prior to this
1812 * operation */
1813 if (aTask.machineStateBackup == MachineState_Saved)
1814 {
1815 Assert(!mSSData->strStateFilePath.isEmpty());
1816
1817 // release the saved state file AFTER unsetting the member variable
1818 // so that releaseSavedStateFile() won't think it's still in use
1819 Utf8Str strStateFile(mSSData->strStateFilePath);
1820 mSSData->strStateFilePath.setNull();
1821 releaseSavedStateFile(strStateFile, NULL /* pSnapshotToIgnore */ );
1822
1823 aTask.modifyBackedUpState(MachineState_PoweredOff);
1824
1825 rc = saveStateSettings(SaveSTS_StateFilePath);
1826 if (FAILED(rc))
1827 throw rc;
1828 }
1829
1830 RTTIMESPEC snapshotTimeStamp;
1831 RTTimeSpecSetMilli(&snapshotTimeStamp, 0);
1832
1833 {
1834 AutoReadLock snapshotLock(aTask.pSnapshot COMMA_LOCKVAL_SRC_POS);
1835
1836 /* remember the timestamp of the snapshot we're restoring from */
1837 snapshotTimeStamp = aTask.pSnapshot->getTimeStamp();
1838
1839 ComPtr<SnapshotMachine> pSnapshotMachine(aTask.pSnapshot->getSnapshotMachine());
1840
1841 /* copy all hardware data from the snapshot */
1842 copyFrom(pSnapshotMachine);
1843
1844 LogFlowThisFunc(("Restoring hard disks from the snapshot...\n"));
1845
1846 // restore the attachments from the snapshot
1847 setModified(IsModified_Storage);
1848 mMediaData.backup();
1849 mMediaData->mAttachments.clear();
1850 for (MediaData::AttachmentList::const_iterator it = pSnapshotMachine->mMediaData->mAttachments.begin();
1851 it != pSnapshotMachine->mMediaData->mAttachments.end();
1852 ++it)
1853 {
1854 ComObjPtr<MediumAttachment> pAttach;
1855 pAttach.createObject();
1856 pAttach->initCopy(this, *it);
1857 mMediaData->mAttachments.push_back(pAttach);
1858 }
1859
1860 /* release the locks before the potentially lengthy operation */
1861 snapshotLock.release();
1862 alock.release();
1863
1864 rc = createImplicitDiffs(aTask.pProgress,
1865 1,
1866 false /* aOnline */);
1867 if (FAILED(rc))
1868 throw rc;
1869
1870 alock.acquire();
1871 snapshotLock.acquire();
1872
1873 /* Note: on success, current (old) hard disks will be
1874 * deassociated/deleted on #commit() called from #saveSettings() at
1875 * the end. On failure, newly created implicit diffs will be
1876 * deleted by #rollback() at the end. */
1877
1878 /* should not have a saved state file associated at this point */
1879 Assert(mSSData->strStateFilePath.isEmpty());
1880
1881 const Utf8Str &strSnapshotStateFile = aTask.pSnapshot->getStateFilePath();
1882
1883 if (strSnapshotStateFile.isNotEmpty())
1884 // online snapshot: then share the state file
1885 mSSData->strStateFilePath = strSnapshotStateFile;
1886
1887 LogFlowThisFunc(("Setting new current snapshot {%RTuuid}\n", aTask.pSnapshot->getId().raw()));
1888 /* make the snapshot we restored from the current snapshot */
1889 mData->mCurrentSnapshot = aTask.pSnapshot;
1890 }
1891
1892 /* grab differencing hard disks from the old attachments that will
1893 * become unused and need to be auto-deleted */
1894 std::list< ComObjPtr<MediumAttachment> > llDiffAttachmentsToDelete;
1895
1896 for (MediaData::AttachmentList::const_iterator it = mMediaData.backedUpData()->mAttachments.begin();
1897 it != mMediaData.backedUpData()->mAttachments.end();
1898 ++it)
1899 {
1900 ComObjPtr<MediumAttachment> pAttach = *it;
1901 ComObjPtr<Medium> pMedium = pAttach->getMedium();
1902
1903 /* while the hard disk is attached, the number of children or the
1904 * parent cannot change, so no lock */
1905 if ( !pMedium.isNull()
1906 && pAttach->getType() == DeviceType_HardDisk
1907 && !pMedium->getParent().isNull()
1908 && pMedium->getChildren().size() == 0
1909 )
1910 {
1911 LogFlowThisFunc(("Picked differencing image '%s' for deletion\n", pMedium->getName().c_str()));
1912
1913 llDiffAttachmentsToDelete.push_back(pAttach);
1914 }
1915 }
1916
1917 /* we have already deleted the current state, so set the execution
1918 * state accordingly no matter of the delete snapshot result */
1919 if (mSSData->strStateFilePath.isNotEmpty())
1920 setMachineState(MachineState_Saved);
1921 else
1922 setMachineState(MachineState_PoweredOff);
1923
1924 updateMachineStateOnClient();
1925 stateRestored = true;
1926
1927 /* Paranoia: no one must have saved the settings in the mean time. If
1928 * it happens nevertheless we'll close our eyes and continue below. */
1929 Assert(mMediaData.isBackedUp());
1930
1931 /* assign the timestamp from the snapshot */
1932 Assert(RTTimeSpecGetMilli(&snapshotTimeStamp) != 0);
1933 mData->mLastStateChange = snapshotTimeStamp;
1934
1935 // detach the current-state diffs that we detected above and build a list of
1936 // image files to delete _after_ saveSettings()
1937
1938 MediaList llDiffsToDelete;
1939
1940 for (std::list< ComObjPtr<MediumAttachment> >::iterator it = llDiffAttachmentsToDelete.begin();
1941 it != llDiffAttachmentsToDelete.end();
1942 ++it)
1943 {
1944 ComObjPtr<MediumAttachment> pAttach = *it; // guaranteed to have only attachments where medium != NULL
1945 ComObjPtr<Medium> pMedium = pAttach->getMedium();
1946
1947 AutoWriteLock mlock(pMedium COMMA_LOCKVAL_SRC_POS);
1948
1949 LogFlowThisFunc(("Detaching old current state in differencing image '%s'\n", pMedium->getName().c_str()));
1950
1951 // Normally we "detach" the medium by removing the attachment object
1952 // from the current machine data; saveSettings() below would then
1953 // compare the current machine data with the one in the backup
1954 // and actually call Medium::removeBackReference(). But that works only half
1955 // the time in our case so instead we force a detachment here:
1956 // remove from machine data
1957 mMediaData->mAttachments.remove(pAttach);
1958 // Remove it from the backup or else saveSettings will try to detach
1959 // it again and assert. The paranoia check avoids crashes (see
1960 // assert above) if this code is buggy and saves settings in the
1961 // wrong place.
1962 if (mMediaData.isBackedUp())
1963 mMediaData.backedUpData()->mAttachments.remove(pAttach);
1964 // then clean up backrefs
1965 pMedium->removeBackReference(mData->mUuid);
1966
1967 llDiffsToDelete.push_back(pMedium);
1968 }
1969
1970 // save machine settings, reset the modified flag and commit;
1971 bool fNeedsGlobalSaveSettings = false;
1972 rc = saveSettings(&fNeedsGlobalSaveSettings,
1973 SaveS_ResetCurStateModified);
1974 if (FAILED(rc))
1975 throw rc;
1976 // unconditionally add the parent registry. We do similar in SessionMachine::EndTakingSnapshot
1977 // (mParent->saveSettings())
1978
1979 // release the locks before updating registry and deleting image files
1980 alock.release();
1981
1982 mParent->markRegistryModified(mParent->getGlobalRegistryId());
1983
1984 // from here on we cannot roll back on failure any more
1985
1986 for (MediaList::iterator it = llDiffsToDelete.begin();
1987 it != llDiffsToDelete.end();
1988 ++it)
1989 {
1990 ComObjPtr<Medium> &pMedium = *it;
1991 LogFlowThisFunc(("Deleting old current state in differencing image '%s'\n", pMedium->getName().c_str()));
1992
1993 HRESULT rc2 = pMedium->deleteStorage(NULL /* aProgress */,
1994 true /* aWait */);
1995 // ignore errors here because we cannot roll back after saveSettings() above
1996 if (SUCCEEDED(rc2))
1997 pMedium->uninit();
1998 }
1999 }
2000 catch (HRESULT aRC)
2001 {
2002 rc = aRC;
2003 }
2004
2005 if (FAILED(rc))
2006 {
2007 /* preserve existing error info */
2008 ErrorInfoKeeper eik;
2009
2010 /* undo all changes on failure */
2011 rollback(false /* aNotify */);
2012
2013 if (!stateRestored)
2014 {
2015 /* restore the machine state */
2016 setMachineState(aTask.machineStateBackup);
2017 updateMachineStateOnClient();
2018 }
2019 }
2020
2021 mParent->saveModifiedRegistries();
2022
2023 /* set the result (this will try to fetch current error info on failure) */
2024 aTask.pProgress->notifyComplete(rc);
2025
2026 if (SUCCEEDED(rc))
2027 mParent->onSnapshotDeleted(mData->mUuid, Guid());
2028
2029 LogFlowThisFunc(("Done restoring snapshot (rc=%08X)\n", rc));
2030
2031 LogFlowThisFuncLeave();
2032}
2033
2034////////////////////////////////////////////////////////////////////////////////
2035//
2036// DeleteSnapshot methods (SessionMachine and related tasks)
2037//
2038////////////////////////////////////////////////////////////////////////////////
2039
2040/**
2041 * Implementation for IInternalMachineControl::deleteSnapshot().
2042 *
2043 * Gets called from Console::DeleteSnapshot(), and that's basically the
2044 * only thing Console does initially. Deleting a snapshot happens entirely on
2045 * the server side if the machine is not running, and if it is running then
2046 * the individual merges are done via internal session callbacks.
2047 *
2048 * This creates a new thread that does the work and returns a progress
2049 * object to the client which is then returned to the caller of
2050 * Console::DeleteSnapshot().
2051 *
2052 * Actual work then takes place in DeleteSnapshotTask::handler().
2053 *
2054 * @note Locks mParent + this + children objects for writing!
2055 */
2056STDMETHODIMP SessionMachine::DeleteSnapshot(IConsole *aInitiator,
2057 IN_BSTR aStartId,
2058 IN_BSTR aEndId,
2059 BOOL fDeleteAllChildren,
2060 MachineState_T *aMachineState,
2061 IProgress **aProgress)
2062{
2063 LogFlowThisFuncEnter();
2064
2065 Guid startId(aStartId);
2066 Guid endId(aEndId);
2067
2068 AssertReturn(aInitiator && !startId.isZero() && !endId.isZero() && startId.isValid() && endId.isValid(), E_INVALIDARG);
2069
2070 AssertReturn(aMachineState && aProgress, E_POINTER);
2071
2072 /** @todo implement the "and all children" and "range" variants */
2073 if (fDeleteAllChildren || startId != endId)
2074 ReturnComNotImplemented();
2075
2076 AutoCaller autoCaller(this);
2077 AssertComRCReturn(autoCaller.rc(), autoCaller.rc());
2078
2079 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
2080
2081 // be very picky about machine states
2082 if ( Global::IsOnlineOrTransient(mData->mMachineState)
2083 && mData->mMachineState != MachineState_PoweredOff
2084 && mData->mMachineState != MachineState_Saved
2085 && mData->mMachineState != MachineState_Teleported
2086 && mData->mMachineState != MachineState_Aborted
2087 && mData->mMachineState != MachineState_Running
2088 && mData->mMachineState != MachineState_Paused)
2089 return setError(VBOX_E_INVALID_VM_STATE,
2090 tr("Invalid machine state: %s"),
2091 Global::stringifyMachineState(mData->mMachineState));
2092
2093 ComObjPtr<Snapshot> pSnapshot;
2094 HRESULT rc = findSnapshotById(startId, pSnapshot, true /* aSetError */);
2095 if (FAILED(rc)) return rc;
2096
2097 AutoWriteLock snapshotLock(pSnapshot COMMA_LOCKVAL_SRC_POS);
2098
2099 size_t childrenCount = pSnapshot->getChildrenCount();
2100 if (childrenCount > 1)
2101 return setError(VBOX_E_INVALID_OBJECT_STATE,
2102 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"),
2103 pSnapshot->getName().c_str(),
2104 mUserData->s.strName.c_str(),
2105 childrenCount);
2106
2107 /* If the snapshot being deleted is the current one, ensure current
2108 * settings are committed and saved.
2109 */
2110 if (pSnapshot == mData->mCurrentSnapshot)
2111 {
2112 if (mData->flModifications)
2113 {
2114 rc = saveSettings(NULL);
2115 // no need to change for whether VirtualBox.xml needs saving since
2116 // we can't have a machine XML rename pending at this point
2117 if (FAILED(rc)) return rc;
2118 }
2119 }
2120
2121 ComObjPtr<SnapshotMachine> pSnapMachine = pSnapshot->getSnapshotMachine();
2122
2123 /* create a progress object. The number of operations is:
2124 * 1 (preparing) + 1 if the snapshot is online + # of normal hard disks
2125 */
2126 LogFlowThisFunc(("Going thru snapshot machine attachments to determine progress setup\n"));
2127
2128 ULONG ulOpCount = 1; // one for preparations
2129 ULONG ulTotalWeight = 1; // one for preparations
2130
2131 if (pSnapshot->getStateFilePath().length())
2132 {
2133 ++ulOpCount;
2134 ++ulTotalWeight; // assume 1 MB for deleting the state file
2135 }
2136
2137 // count normal hard disks and add their sizes to the weight
2138 for (MediaData::AttachmentList::iterator it = pSnapMachine->mMediaData->mAttachments.begin();
2139 it != pSnapMachine->mMediaData->mAttachments.end();
2140 ++it)
2141 {
2142 ComObjPtr<MediumAttachment> &pAttach = *it;
2143 AutoReadLock attachLock(pAttach COMMA_LOCKVAL_SRC_POS);
2144 if (pAttach->getType() == DeviceType_HardDisk)
2145 {
2146 ComObjPtr<Medium> pHD = pAttach->getMedium();
2147 Assert(pHD);
2148 AutoReadLock mlock(pHD COMMA_LOCKVAL_SRC_POS);
2149
2150 MediumType_T type = pHD->getType();
2151 // writethrough and shareable images are unaffected by snapshots,
2152 // so do nothing for them
2153 if ( type != MediumType_Writethrough
2154 && type != MediumType_Shareable
2155 && type != MediumType_Readonly)
2156 {
2157 // normal or immutable media need attention
2158 ++ulOpCount;
2159 ulTotalWeight += (ULONG)(pHD->getSize() / _1M);
2160 }
2161 LogFlowThisFunc(("op %d: considering hard disk attachment %s\n", ulOpCount, pHD->getName().c_str()));
2162 }
2163 }
2164
2165 ComObjPtr<Progress> pProgress;
2166 pProgress.createObject();
2167 pProgress->init(mParent, aInitiator,
2168 BstrFmt(tr("Deleting snapshot '%s'"), pSnapshot->getName().c_str()).raw(),
2169 FALSE /* aCancelable */,
2170 ulOpCount,
2171 ulTotalWeight,
2172 Bstr(tr("Setting up")).raw(),
2173 1);
2174
2175 bool fDeleteOnline = ( (mData->mMachineState == MachineState_Running)
2176 || (mData->mMachineState == MachineState_Paused));
2177
2178 /* create and start the task on a separate thread */
2179 DeleteSnapshotTask *task = new DeleteSnapshotTask(this, pProgress,
2180 fDeleteOnline, pSnapshot);
2181 int vrc = RTThreadCreate(NULL,
2182 taskHandler,
2183 (void*)task,
2184 0,
2185 RTTHREADTYPE_MAIN_WORKER,
2186 0,
2187 "DeleteSnapshot");
2188 if (RT_FAILURE(vrc))
2189 {
2190 delete task;
2191 return E_FAIL;
2192 }
2193
2194 // the task might start running but will block on acquiring the machine's write lock
2195 // which we acquired above; once this function leaves, the task will be unblocked;
2196 // set the proper machine state here now (note: after creating a Task instance)
2197 if (mData->mMachineState == MachineState_Running)
2198 setMachineState(MachineState_DeletingSnapshotOnline);
2199 else if (mData->mMachineState == MachineState_Paused)
2200 setMachineState(MachineState_DeletingSnapshotPaused);
2201 else
2202 setMachineState(MachineState_DeletingSnapshot);
2203
2204 /* return the progress to the caller */
2205 pProgress.queryInterfaceTo(aProgress);
2206
2207 /* return the new state to the caller */
2208 *aMachineState = mData->mMachineState;
2209
2210 LogFlowThisFuncLeave();
2211
2212 return S_OK;
2213}
2214
2215/**
2216 * Helper struct for SessionMachine::deleteSnapshotHandler().
2217 */
2218struct MediumDeleteRec
2219{
2220 MediumDeleteRec()
2221 : mfNeedsOnlineMerge(false),
2222 mpMediumLockList(NULL)
2223 {}
2224
2225 MediumDeleteRec(const ComObjPtr<Medium> &aHd,
2226 const ComObjPtr<Medium> &aSource,
2227 const ComObjPtr<Medium> &aTarget,
2228 const ComObjPtr<MediumAttachment> &aOnlineMediumAttachment,
2229 bool fMergeForward,
2230 const ComObjPtr<Medium> &aParentForTarget,
2231 const MediaList &aChildrenToReparent,
2232 bool fNeedsOnlineMerge,
2233 MediumLockList *aMediumLockList)
2234 : mpHD(aHd),
2235 mpSource(aSource),
2236 mpTarget(aTarget),
2237 mpOnlineMediumAttachment(aOnlineMediumAttachment),
2238 mfMergeForward(fMergeForward),
2239 mpParentForTarget(aParentForTarget),
2240 mChildrenToReparent(aChildrenToReparent),
2241 mfNeedsOnlineMerge(fNeedsOnlineMerge),
2242 mpMediumLockList(aMediumLockList)
2243 {}
2244
2245 MediumDeleteRec(const ComObjPtr<Medium> &aHd,
2246 const ComObjPtr<Medium> &aSource,
2247 const ComObjPtr<Medium> &aTarget,
2248 const ComObjPtr<MediumAttachment> &aOnlineMediumAttachment,
2249 bool fMergeForward,
2250 const ComObjPtr<Medium> &aParentForTarget,
2251 const MediaList &aChildrenToReparent,
2252 bool fNeedsOnlineMerge,
2253 MediumLockList *aMediumLockList,
2254 const Guid &aMachineId,
2255 const Guid &aSnapshotId)
2256 : mpHD(aHd),
2257 mpSource(aSource),
2258 mpTarget(aTarget),
2259 mpOnlineMediumAttachment(aOnlineMediumAttachment),
2260 mfMergeForward(fMergeForward),
2261 mpParentForTarget(aParentForTarget),
2262 mChildrenToReparent(aChildrenToReparent),
2263 mfNeedsOnlineMerge(fNeedsOnlineMerge),
2264 mpMediumLockList(aMediumLockList),
2265 mMachineId(aMachineId),
2266 mSnapshotId(aSnapshotId)
2267 {}
2268
2269 ComObjPtr<Medium> mpHD;
2270 ComObjPtr<Medium> mpSource;
2271 ComObjPtr<Medium> mpTarget;
2272 ComObjPtr<MediumAttachment> mpOnlineMediumAttachment;
2273 bool mfMergeForward;
2274 ComObjPtr<Medium> mpParentForTarget;
2275 MediaList mChildrenToReparent;
2276 bool mfNeedsOnlineMerge;
2277 MediumLockList *mpMediumLockList;
2278 /* these are for reattaching the hard disk in case of a failure: */
2279 Guid mMachineId;
2280 Guid mSnapshotId;
2281};
2282
2283typedef std::list<MediumDeleteRec> MediumDeleteRecList;
2284
2285/**
2286 * Worker method for the delete snapshot thread created by
2287 * SessionMachine::DeleteSnapshot(). This method gets called indirectly
2288 * through SessionMachine::taskHandler() which then calls
2289 * DeleteSnapshotTask::handler().
2290 *
2291 * The DeleteSnapshotTask contains the progress object returned to the console
2292 * by SessionMachine::DeleteSnapshot, through which progress and results are
2293 * reported.
2294 *
2295 * SessionMachine::DeleteSnapshot() has set the machine state to
2296 * MachineState_DeletingSnapshot right after creating this task. Since we block
2297 * on the machine write lock at the beginning, once that has been acquired, we
2298 * can assume that the machine state is indeed that.
2299 *
2300 * @note Locks the machine + the snapshot + the media tree for writing!
2301 *
2302 * @param aTask Task data.
2303 */
2304
2305void SessionMachine::deleteSnapshotHandler(DeleteSnapshotTask &aTask)
2306{
2307 LogFlowThisFuncEnter();
2308
2309 AutoCaller autoCaller(this);
2310
2311 LogFlowThisFunc(("state=%d\n", autoCaller.state()));
2312 if (!autoCaller.isOk())
2313 {
2314 /* we might have been uninitialized because the session was accidentally
2315 * closed by the client, so don't assert */
2316 aTask.pProgress->notifyComplete(E_FAIL,
2317 COM_IIDOF(IMachine),
2318 getComponentName(),
2319 tr("The session has been accidentally closed"));
2320 LogFlowThisFuncLeave();
2321 return;
2322 }
2323
2324 HRESULT rc = S_OK;
2325 MediumDeleteRecList toDelete;
2326 Guid snapshotId;
2327
2328 try
2329 {
2330 /* Locking order: */
2331 AutoMultiWriteLock2 multiLock(this->lockHandle(), // machine
2332 aTask.pSnapshot->lockHandle() // snapshot
2333 COMMA_LOCKVAL_SRC_POS);
2334 // once we have this lock, we know that SessionMachine::DeleteSnapshot()
2335 // has exited after setting the machine state to MachineState_DeletingSnapshot
2336
2337 AutoWriteLock treeLock(mParent->getMediaTreeLockHandle()
2338 COMMA_LOCKVAL_SRC_POS);
2339
2340 ComObjPtr<SnapshotMachine> pSnapMachine = aTask.pSnapshot->getSnapshotMachine();
2341 // no need to lock the snapshot machine since it is const by definition
2342 Guid machineId = pSnapMachine->getId();
2343
2344 // save the snapshot ID (for callbacks)
2345 snapshotId = aTask.pSnapshot->getId();
2346
2347 // first pass:
2348 LogFlowThisFunc(("1: Checking hard disk merge prerequisites...\n"));
2349
2350 // Go thru the attachments of the snapshot machine (the media in here
2351 // point to the disk states _before_ the snapshot was taken, i.e. the
2352 // state we're restoring to; for each such medium, we will need to
2353 // merge it with its one and only child (the diff image holding the
2354 // changes written after the snapshot was taken).
2355 for (MediaData::AttachmentList::iterator it = pSnapMachine->mMediaData->mAttachments.begin();
2356 it != pSnapMachine->mMediaData->mAttachments.end();
2357 ++it)
2358 {
2359 ComObjPtr<MediumAttachment> &pAttach = *it;
2360 AutoReadLock attachLock(pAttach COMMA_LOCKVAL_SRC_POS);
2361 if (pAttach->getType() != DeviceType_HardDisk)
2362 continue;
2363
2364 ComObjPtr<Medium> pHD = pAttach->getMedium();
2365 Assert(!pHD.isNull());
2366
2367 {
2368 // writethrough, shareable and readonly images are
2369 // unaffected by snapshots, skip them
2370 AutoReadLock medlock(pHD COMMA_LOCKVAL_SRC_POS);
2371 MediumType_T type = pHD->getType();
2372 if ( type == MediumType_Writethrough
2373 || type == MediumType_Shareable
2374 || type == MediumType_Readonly)
2375 continue;
2376 }
2377
2378#ifdef DEBUG
2379 pHD->dumpBackRefs();
2380#endif
2381
2382 // needs to be merged with child or deleted, check prerequisites
2383 ComObjPtr<Medium> pTarget;
2384 ComObjPtr<Medium> pSource;
2385 bool fMergeForward = false;
2386 ComObjPtr<Medium> pParentForTarget;
2387 MediaList childrenToReparent;
2388 bool fNeedsOnlineMerge = false;
2389 bool fOnlineMergePossible = aTask.m_fDeleteOnline;
2390 MediumLockList *pMediumLockList = NULL;
2391 MediumLockList *pVMMALockList = NULL;
2392 ComObjPtr<MediumAttachment> pOnlineMediumAttachment;
2393 if (fOnlineMergePossible)
2394 {
2395 // Look up the corresponding medium attachment in the currently
2396 // running VM. Any failure prevents a live merge. Could be made
2397 // a tad smarter by trying a few candidates, so that e.g. disks
2398 // which are simply moved to a different controller slot do not
2399 // prevent online merging in general.
2400 pOnlineMediumAttachment =
2401 findAttachment(mMediaData->mAttachments,
2402 pAttach->getControllerName().raw(),
2403 pAttach->getPort(),
2404 pAttach->getDevice());
2405 if (pOnlineMediumAttachment)
2406 {
2407 rc = mData->mSession.mLockedMedia.Get(pOnlineMediumAttachment,
2408 pVMMALockList);
2409 if (FAILED(rc))
2410 fOnlineMergePossible = false;
2411 }
2412 else
2413 fOnlineMergePossible = false;
2414 }
2415
2416 // no need to hold the lock any longer
2417 attachLock.release();
2418
2419 treeLock.release();
2420 rc = prepareDeleteSnapshotMedium(pHD, machineId, snapshotId,
2421 fOnlineMergePossible,
2422 pVMMALockList, pSource, pTarget,
2423 fMergeForward, pParentForTarget,
2424 childrenToReparent,
2425 fNeedsOnlineMerge,
2426 pMediumLockList);
2427 treeLock.acquire();
2428 if (FAILED(rc))
2429 throw rc;
2430
2431 // For simplicity, prepareDeleteSnapshotMedium selects the merge
2432 // direction in the following way: we merge pHD onto its child
2433 // (forward merge), not the other way round, because that saves us
2434 // from unnecessarily shuffling around the attachments for the
2435 // machine that follows the snapshot (next snapshot or current
2436 // state), unless it's a base image. Backwards merges of the first
2437 // snapshot into the base image is essential, as it ensures that
2438 // when all snapshots are deleted the only remaining image is a
2439 // base image. Important e.g. for medium formats which do not have
2440 // a file representation such as iSCSI.
2441
2442 // a couple paranoia checks for backward merges
2443 if (pMediumLockList != NULL && !fMergeForward)
2444 {
2445 // parent is null -> this disk is a base hard disk: we will
2446 // then do a backward merge, i.e. merge its only child onto the
2447 // base disk. Here we need then to update the attachment that
2448 // refers to the child and have it point to the parent instead
2449 Assert(pHD->getParent().isNull());
2450 Assert(pHD->getChildren().size() == 1);
2451
2452 ComObjPtr<Medium> pReplaceHD = pHD->getChildren().front();
2453
2454 ComAssertThrow(pReplaceHD == pSource, E_FAIL);
2455 }
2456
2457 Guid replaceMachineId;
2458 Guid replaceSnapshotId;
2459
2460 const Guid *pReplaceMachineId = pSource->getFirstMachineBackrefId();
2461 // minimal sanity checking
2462 Assert(!pReplaceMachineId || *pReplaceMachineId == mData->mUuid);
2463 if (pReplaceMachineId)
2464 replaceMachineId = *pReplaceMachineId;
2465
2466 const Guid *pSnapshotId = pSource->getFirstMachineBackrefSnapshotId();
2467 if (pSnapshotId)
2468 replaceSnapshotId = *pSnapshotId;
2469
2470 if (replaceMachineId.isValid() && !replaceMachineId.isZero())
2471 {
2472 // Adjust the backreferences, otherwise merging will assert.
2473 // Note that the medium attachment object stays associated
2474 // with the snapshot until the merge was successful.
2475 HRESULT rc2 = S_OK;
2476 rc2 = pSource->removeBackReference(replaceMachineId, replaceSnapshotId);
2477 AssertComRC(rc2);
2478
2479 toDelete.push_back(MediumDeleteRec(pHD, pSource, pTarget,
2480 pOnlineMediumAttachment,
2481 fMergeForward,
2482 pParentForTarget,
2483 childrenToReparent,
2484 fNeedsOnlineMerge,
2485 pMediumLockList,
2486 replaceMachineId,
2487 replaceSnapshotId));
2488 }
2489 else
2490 toDelete.push_back(MediumDeleteRec(pHD, pSource, pTarget,
2491 pOnlineMediumAttachment,
2492 fMergeForward,
2493 pParentForTarget,
2494 childrenToReparent,
2495 fNeedsOnlineMerge,
2496 pMediumLockList));
2497 }
2498
2499 {/* see @bugref{4386} */
2500 /*check available place on the storage*/
2501 RTFOFF pcbTotal = 0;
2502 RTFOFF pcbFree = 0;
2503 uint32_t pcbBlock = 0;
2504 uint32_t pcbSector = 0;
2505 std::multimap<uint32_t,uint64_t> neededStorageFreeSpace;
2506 std::map<uint32_t,const char*> serialMapToStoragePath;
2507
2508 MediumDeleteRecList::const_iterator it_md = toDelete.begin();
2509
2510 while (it_md != toDelete.end())
2511 {
2512 uint64_t diskSize = 0;
2513 uint32_t pu32Serial = 0;
2514 ComObjPtr<Medium> pSource_local = it_md->mpSource;
2515 ComObjPtr<Medium> pTarget_local = it_md->mpTarget;
2516 ComPtr<IMediumFormat> pTargetFormat;
2517
2518 rc = pTarget_local->COMGETTER(MediumFormat)(pTargetFormat.asOutParam());
2519 if (FAILED(rc))
2520 throw rc;
2521 ULONG uTargetCaps = 0;
2522 rc = pTargetFormat->COMGETTER(Capabilities)(&uTargetCaps);
2523 if (FAILED(rc))
2524 throw rc;
2525
2526 if (uTargetCaps & MediumFormatCapabilities_File)
2527 {
2528 int vrc = RTFsQuerySerial(pTarget_local->getLocationFull().c_str(), &pu32Serial);
2529 if (RT_FAILURE(vrc))
2530 {
2531 rc = setError(E_FAIL,
2532 tr(" Unable to merge storage '%s'. Can't get storage UID "),
2533 pTarget_local->getLocationFull().c_str());
2534 throw rc;
2535 }
2536
2537 pSource_local->COMGETTER(Size)((LONG64*)&diskSize);
2538
2539 /* store needed free space in multimap */
2540 neededStorageFreeSpace.insert(std::make_pair(pu32Serial,diskSize));
2541 /* linking storage UID with snapshot path, it is a helper container (just for easy finding needed path) */
2542 serialMapToStoragePath.insert(std::make_pair(pu32Serial,pTarget_local->getLocationFull().c_str()));
2543 }
2544
2545 ++it_md;
2546 }
2547
2548 while (!neededStorageFreeSpace.empty())
2549 {
2550 std::pair<std::multimap<uint32_t,uint64_t>::iterator,std::multimap<uint32_t,uint64_t>::iterator> ret;
2551 uint64_t commonSourceStoragesSize = 0;
2552
2553 /* find all records in multimap with identical storage UID*/
2554 ret = neededStorageFreeSpace.equal_range(neededStorageFreeSpace.begin()->first);
2555 std::multimap<uint32_t,uint64_t>::const_iterator it_ns = ret.first;
2556
2557 for (; it_ns != ret.second ; ++it_ns)
2558 {
2559 commonSourceStoragesSize += it_ns->second;
2560 }
2561
2562 /* find appropriate path by storage UID*/
2563 std::map<uint32_t,const char*>::const_iterator it_sm = serialMapToStoragePath.find(ret.first->first);
2564 /* get info about a storage */
2565 if (it_sm == serialMapToStoragePath.end())
2566 {
2567 LogFlowThisFunc((" Path to the storage wasn't found...\n "));
2568
2569 rc = setError(E_INVALIDARG,
2570 tr(" Unable to merge storage '%s'. Path to the storage wasn't found. "),
2571 it_sm->second);
2572 throw rc;
2573 }
2574
2575 int vrc = RTFsQuerySizes(it_sm->second, &pcbTotal, &pcbFree,&pcbBlock, &pcbSector);
2576 if (RT_FAILURE(vrc))
2577 {
2578 rc = setError(E_FAIL,
2579 tr(" Unable to merge storage '%s'. Can't get the storage size. "),
2580 it_sm->second);
2581 throw rc;
2582 }
2583
2584 if (commonSourceStoragesSize > (uint64_t)pcbFree)
2585 {
2586 LogFlowThisFunc((" Not enough free space to merge...\n "));
2587
2588 rc = setError(E_OUTOFMEMORY,
2589 tr(" Unable to merge storage '%s' - not enough free storage space. "),
2590 it_sm->second);
2591 throw rc;
2592 }
2593
2594 neededStorageFreeSpace.erase(ret.first, ret.second);
2595 }
2596
2597 serialMapToStoragePath.clear();
2598 }
2599
2600 // we can release the locks now since the machine state is MachineState_DeletingSnapshot
2601 treeLock.release();
2602 multiLock.release();
2603
2604 /* Now we checked that we can successfully merge all normal hard disks
2605 * (unless a runtime error like end-of-disc happens). Now get rid of
2606 * the saved state (if present), as that will free some disk space.
2607 * The snapshot itself will be deleted as late as possible, so that
2608 * the user can repeat the delete operation if he runs out of disk
2609 * space or cancels the delete operation. */
2610
2611 /* second pass: */
2612 LogFlowThisFunc(("2: Deleting saved state...\n"));
2613
2614 {
2615 // saveAllSnapshots() needs a machine lock, and the snapshots
2616 // tree is protected by the machine lock as well
2617 AutoWriteLock machineLock(this COMMA_LOCKVAL_SRC_POS);
2618
2619 Utf8Str stateFilePath = aTask.pSnapshot->getStateFilePath();
2620 if (!stateFilePath.isEmpty())
2621 {
2622 aTask.pProgress->SetNextOperation(Bstr(tr("Deleting the execution state")).raw(),
2623 1); // weight
2624
2625 releaseSavedStateFile(stateFilePath, aTask.pSnapshot /* pSnapshotToIgnore */);
2626
2627 // machine will need saving now
2628 machineLock.release();
2629 mParent->markRegistryModified(getId());
2630 }
2631 }
2632
2633 /* third pass: */
2634 LogFlowThisFunc(("3: Performing actual hard disk merging...\n"));
2635
2636 /// @todo NEWMEDIA turn the following errors into warnings because the
2637 /// snapshot itself has been already deleted (and interpret these
2638 /// warnings properly on the GUI side)
2639 for (MediumDeleteRecList::iterator it = toDelete.begin();
2640 it != toDelete.end();)
2641 {
2642 const ComObjPtr<Medium> &pMedium(it->mpHD);
2643 ULONG ulWeight;
2644
2645 {
2646 AutoReadLock alock(pMedium COMMA_LOCKVAL_SRC_POS);
2647 ulWeight = (ULONG)(pMedium->getSize() / _1M);
2648 }
2649
2650 aTask.pProgress->SetNextOperation(BstrFmt(tr("Merging differencing image '%s'"),
2651 pMedium->getName().c_str()).raw(),
2652 ulWeight);
2653
2654 bool fNeedSourceUninit = false;
2655 bool fReparentTarget = false;
2656 if (it->mpMediumLockList == NULL)
2657 {
2658 /* no real merge needed, just updating state and delete
2659 * diff files if necessary */
2660 AutoMultiWriteLock2 mLock(&mParent->getMediaTreeLockHandle(), pMedium->lockHandle() COMMA_LOCKVAL_SRC_POS);
2661
2662 Assert( !it->mfMergeForward
2663 || pMedium->getChildren().size() == 0);
2664
2665 /* Delete the differencing hard disk (has no children). Two
2666 * exceptions: if it's the last medium in the chain or if it's
2667 * a backward merge we don't want to handle due to complexity.
2668 * In both cases leave the image in place. If it's the first
2669 * exception the user can delete it later if he wants. */
2670 if (!pMedium->getParent().isNull())
2671 {
2672 Assert(pMedium->getState() == MediumState_Deleting);
2673 /* No need to hold the lock any longer. */
2674 mLock.release();
2675 rc = pMedium->deleteStorage(&aTask.pProgress,
2676 true /* aWait */);
2677 if (FAILED(rc))
2678 throw rc;
2679
2680 // need to uninit the deleted medium
2681 fNeedSourceUninit = true;
2682 }
2683 }
2684 else
2685 {
2686 bool fNeedsSave = false;
2687 if (it->mfNeedsOnlineMerge)
2688 {
2689 // online medium merge, in the direction decided earlier
2690 rc = onlineMergeMedium(it->mpOnlineMediumAttachment,
2691 it->mpSource,
2692 it->mpTarget,
2693 it->mfMergeForward,
2694 it->mpParentForTarget,
2695 it->mChildrenToReparent,
2696 it->mpMediumLockList,
2697 aTask.pProgress,
2698 &fNeedsSave);
2699 }
2700 else
2701 {
2702 // normal medium merge, in the direction decided earlier
2703 rc = it->mpSource->mergeTo(it->mpTarget,
2704 it->mfMergeForward,
2705 it->mpParentForTarget,
2706 it->mChildrenToReparent,
2707 it->mpMediumLockList,
2708 &aTask.pProgress,
2709 true /* aWait */);
2710 }
2711
2712 // If the merge failed, we need to do our best to have a usable
2713 // VM configuration afterwards. The return code doesn't tell
2714 // whether the merge completed and so we have to check if the
2715 // source medium (diff images are always file based at the
2716 // moment) is still there or not. Be careful not to lose the
2717 // error code below, before the "Delayed failure exit".
2718 if (FAILED(rc))
2719 {
2720 AutoReadLock mlock(it->mpSource COMMA_LOCKVAL_SRC_POS);
2721 if (!it->mpSource->isMediumFormatFile())
2722 // Diff medium not backed by a file - cannot get status so
2723 // be pessimistic.
2724 throw rc;
2725 const Utf8Str &loc = it->mpSource->getLocationFull();
2726 // Source medium is still there, so merge failed early.
2727 if (RTFileExists(loc.c_str()))
2728 throw rc;
2729
2730 // Source medium is gone. Assume the merge succeeded and
2731 // thus it's safe to remove the attachment. We use the
2732 // "Delayed failure exit" below.
2733 }
2734
2735 // need to change the medium attachment for backward merges
2736 fReparentTarget = !it->mfMergeForward;
2737
2738 if (!it->mfNeedsOnlineMerge)
2739 {
2740 // need to uninit the medium deleted by the merge
2741 fNeedSourceUninit = true;
2742
2743 // delete the no longer needed medium lock list, which
2744 // implicitly handled the unlocking
2745 delete it->mpMediumLockList;
2746 it->mpMediumLockList = NULL;
2747 }
2748 }
2749
2750 // Now that the medium is successfully merged/deleted/whatever,
2751 // remove the medium attachment from the snapshot. For a backwards
2752 // merge the target attachment needs to be removed from the
2753 // snapshot, as the VM will take it over. For forward merges the
2754 // source medium attachment needs to be removed.
2755 ComObjPtr<MediumAttachment> pAtt;
2756 if (fReparentTarget)
2757 {
2758 pAtt = findAttachment(pSnapMachine->mMediaData->mAttachments,
2759 it->mpTarget);
2760 it->mpTarget->removeBackReference(machineId, snapshotId);
2761 }
2762 else
2763 pAtt = findAttachment(pSnapMachine->mMediaData->mAttachments,
2764 it->mpSource);
2765 pSnapMachine->mMediaData->mAttachments.remove(pAtt);
2766
2767 if (fReparentTarget)
2768 {
2769 // Search for old source attachment and replace with target.
2770 // There can be only one child snapshot in this case.
2771 ComObjPtr<Machine> pMachine = this;
2772 Guid childSnapshotId;
2773 ComObjPtr<Snapshot> pChildSnapshot = aTask.pSnapshot->getFirstChild();
2774 if (pChildSnapshot)
2775 {
2776 pMachine = pChildSnapshot->getSnapshotMachine();
2777 childSnapshotId = pChildSnapshot->getId();
2778 }
2779 pAtt = findAttachment(pMachine->mMediaData->mAttachments, it->mpSource);
2780 if (pAtt)
2781 {
2782 AutoWriteLock attLock(pAtt COMMA_LOCKVAL_SRC_POS);
2783 pAtt->updateMedium(it->mpTarget);
2784 it->mpTarget->addBackReference(pMachine->mData->mUuid, childSnapshotId);
2785 }
2786 else
2787 {
2788 // If no attachment is found do not change anything. Maybe
2789 // the source medium was not attached to the snapshot.
2790 // If this is an online deletion the attachment was updated
2791 // already to allow the VM continue execution immediately.
2792 // Needs a bit of special treatment due to this difference.
2793 if (it->mfNeedsOnlineMerge)
2794 it->mpTarget->addBackReference(pMachine->mData->mUuid, childSnapshotId);
2795 }
2796 }
2797
2798 if (fNeedSourceUninit)
2799 it->mpSource->uninit();
2800
2801 // One attachment is merged, must save the settings
2802 mParent->markRegistryModified(getId());
2803
2804 // prevent calling cancelDeleteSnapshotMedium() for this attachment
2805 it = toDelete.erase(it);
2806
2807 // Delayed failure exit when the merge cleanup failed but the
2808 // merge actually succeeded.
2809 if (FAILED(rc))
2810 throw rc;
2811 }
2812
2813 {
2814 // beginSnapshotDelete() needs the machine lock, and the snapshots
2815 // tree is protected by the machine lock as well
2816 AutoWriteLock machineLock(this COMMA_LOCKVAL_SRC_POS);
2817
2818 aTask.pSnapshot->beginSnapshotDelete();
2819 aTask.pSnapshot->uninit();
2820
2821 machineLock.release();
2822 mParent->markRegistryModified(getId());
2823 }
2824 }
2825 catch (HRESULT aRC) {
2826 rc = aRC;
2827 }
2828
2829 if (FAILED(rc))
2830 {
2831 // preserve existing error info so that the result can
2832 // be properly reported to the progress object below
2833 ErrorInfoKeeper eik;
2834
2835 AutoMultiWriteLock2 multiLock(this->lockHandle(), // machine
2836 &mParent->getMediaTreeLockHandle() // media tree
2837 COMMA_LOCKVAL_SRC_POS);
2838
2839 // un-prepare the remaining hard disks
2840 for (MediumDeleteRecList::const_iterator it = toDelete.begin();
2841 it != toDelete.end();
2842 ++it)
2843 {
2844 cancelDeleteSnapshotMedium(it->mpHD, it->mpSource,
2845 it->mChildrenToReparent,
2846 it->mfNeedsOnlineMerge,
2847 it->mpMediumLockList, it->mMachineId,
2848 it->mSnapshotId);
2849 }
2850 }
2851
2852 // whether we were successful or not, we need to set the machine
2853 // state and save the machine settings;
2854 {
2855 // preserve existing error info so that the result can
2856 // be properly reported to the progress object below
2857 ErrorInfoKeeper eik;
2858
2859 // restore the machine state that was saved when the
2860 // task was started
2861 setMachineState(aTask.machineStateBackup);
2862 updateMachineStateOnClient();
2863
2864 mParent->saveModifiedRegistries();
2865 }
2866
2867 // report the result (this will try to fetch current error info on failure)
2868 aTask.pProgress->notifyComplete(rc);
2869
2870 if (SUCCEEDED(rc))
2871 mParent->onSnapshotDeleted(mData->mUuid, snapshotId);
2872
2873 LogFlowThisFunc(("Done deleting snapshot (rc=%08X)\n", rc));
2874 LogFlowThisFuncLeave();
2875}
2876
2877/**
2878 * Checks that this hard disk (part of a snapshot) may be deleted/merged and
2879 * performs necessary state changes. Must not be called for writethrough disks
2880 * because there is nothing to delete/merge then.
2881 *
2882 * This method is to be called prior to calling #deleteSnapshotMedium().
2883 * If #deleteSnapshotMedium() is not called or fails, the state modifications
2884 * performed by this method must be undone by #cancelDeleteSnapshotMedium().
2885 *
2886 * @return COM status code
2887 * @param aHD Hard disk which is connected to the snapshot.
2888 * @param aMachineId UUID of machine this hard disk is attached to.
2889 * @param aSnapshotId UUID of snapshot this hard disk is attached to. May
2890 * be a zero UUID if no snapshot is applicable.
2891 * @param fOnlineMergePossible Flag whether an online merge is possible.
2892 * @param aVMMALockList Medium lock list for the medium attachment of this VM.
2893 * Only used if @a fOnlineMergePossible is @c true, and
2894 * must be non-NULL in this case.
2895 * @param aSource Source hard disk for merge (out).
2896 * @param aTarget Target hard disk for merge (out).
2897 * @param aMergeForward Merge direction decision (out).
2898 * @param aParentForTarget New parent if target needs to be reparented (out).
2899 * @param aChildrenToReparent Children which have to be reparented to the
2900 * target (out).
2901 * @param fNeedsOnlineMerge Whether this merge needs to be done online (out).
2902 * If this is set to @a true then the @a aVMMALockList
2903 * parameter has been modified and is returned as
2904 * @a aMediumLockList.
2905 * @param aMediumLockList Where to store the created medium lock list (may
2906 * return NULL if no real merge is necessary).
2907 *
2908 * @note Caller must hold media tree lock for writing. This locks this object
2909 * and every medium object on the merge chain for writing.
2910 */
2911HRESULT SessionMachine::prepareDeleteSnapshotMedium(const ComObjPtr<Medium> &aHD,
2912 const Guid &aMachineId,
2913 const Guid &aSnapshotId,
2914 bool fOnlineMergePossible,
2915 MediumLockList *aVMMALockList,
2916 ComObjPtr<Medium> &aSource,
2917 ComObjPtr<Medium> &aTarget,
2918 bool &aMergeForward,
2919 ComObjPtr<Medium> &aParentForTarget,
2920 MediaList &aChildrenToReparent,
2921 bool &fNeedsOnlineMerge,
2922 MediumLockList * &aMediumLockList)
2923{
2924 Assert(!mParent->getMediaTreeLockHandle().isWriteLockOnCurrentThread());
2925 Assert(!fOnlineMergePossible || VALID_PTR(aVMMALockList));
2926
2927 AutoWriteLock alock(aHD COMMA_LOCKVAL_SRC_POS);
2928
2929 // Medium must not be writethrough/shareable/readonly at this point
2930 MediumType_T type = aHD->getType();
2931 AssertReturn( type != MediumType_Writethrough
2932 && type != MediumType_Shareable
2933 && type != MediumType_Readonly, E_FAIL);
2934
2935 aMediumLockList = NULL;
2936 fNeedsOnlineMerge = false;
2937
2938 if (aHD->getChildren().size() == 0)
2939 {
2940 /* This technically is no merge, set those values nevertheless.
2941 * Helps with updating the medium attachments. */
2942 aSource = aHD;
2943 aTarget = aHD;
2944
2945 /* special treatment of the last hard disk in the chain: */
2946 if (aHD->getParent().isNull())
2947 {
2948 /* lock only, to prevent any usage until the snapshot deletion
2949 * is completed */
2950 alock.release();
2951 return aHD->LockWrite(NULL);
2952 }
2953
2954 /* the differencing hard disk w/o children will be deleted, protect it
2955 * from attaching to other VMs (this is why Deleting) */
2956 return aHD->markForDeletion();
2957 }
2958
2959 /* not going multi-merge as it's too expensive */
2960 if (aHD->getChildren().size() > 1)
2961 return setError(E_FAIL,
2962 tr("Hard disk '%s' has more than one child hard disk (%d)"),
2963 aHD->getLocationFull().c_str(),
2964 aHD->getChildren().size());
2965
2966 ComObjPtr<Medium> pChild = aHD->getChildren().front();
2967
2968 AutoWriteLock childLock(pChild COMMA_LOCKVAL_SRC_POS);
2969
2970 /* the rest is a normal merge setup */
2971 if (aHD->getParent().isNull())
2972 {
2973 /* base hard disk, backward merge */
2974 const Guid *pMachineId1 = pChild->getFirstMachineBackrefId();
2975 const Guid *pMachineId2 = aHD->getFirstMachineBackrefId();
2976 if (pMachineId1 && pMachineId2 && *pMachineId1 != *pMachineId2)
2977 {
2978 /* backward merge is too tricky, we'll just detach on snapshot
2979 * deletion, so lock only, to prevent any usage */
2980 childLock.release();
2981 alock.release();
2982 return aHD->LockWrite(NULL);
2983 }
2984
2985 aSource = pChild;
2986 aTarget = aHD;
2987 }
2988 else
2989 {
2990 /* forward merge */
2991 aSource = aHD;
2992 aTarget = pChild;
2993 }
2994
2995 HRESULT rc;
2996 childLock.release();
2997 alock.release();
2998 rc = aSource->prepareMergeTo(aTarget, &aMachineId, &aSnapshotId,
2999 !fOnlineMergePossible /* fLockMedia */,
3000 aMergeForward, aParentForTarget,
3001 aChildrenToReparent, aMediumLockList);
3002 alock.acquire();
3003 childLock.acquire();
3004 if (SUCCEEDED(rc) && fOnlineMergePossible)
3005 {
3006 /* Try to lock the newly constructed medium lock list. If it succeeds
3007 * this can be handled as an offline merge, i.e. without the need of
3008 * asking the VM to do the merging. Only continue with the online
3009 * merging preparation if applicable. */
3010 childLock.release();
3011 alock.release();
3012 rc = aMediumLockList->Lock();
3013 alock.acquire();
3014 childLock.acquire();
3015 if (FAILED(rc) && fOnlineMergePossible)
3016 {
3017 /* Locking failed, this cannot be done as an offline merge. Try to
3018 * combine the locking information into the lock list of the medium
3019 * attachment in the running VM. If that fails or locking the
3020 * resulting lock list fails then the merge cannot be done online.
3021 * It can be repeated by the user when the VM is shut down. */
3022 MediumLockList::Base::iterator lockListVMMABegin =
3023 aVMMALockList->GetBegin();
3024 MediumLockList::Base::iterator lockListVMMAEnd =
3025 aVMMALockList->GetEnd();
3026 MediumLockList::Base::iterator lockListBegin =
3027 aMediumLockList->GetBegin();
3028 MediumLockList::Base::iterator lockListEnd =
3029 aMediumLockList->GetEnd();
3030 for (MediumLockList::Base::iterator it = lockListVMMABegin,
3031 it2 = lockListBegin;
3032 it2 != lockListEnd;
3033 ++it, ++it2)
3034 {
3035 if ( it == lockListVMMAEnd
3036 || it->GetMedium() != it2->GetMedium())
3037 {
3038 fOnlineMergePossible = false;
3039 break;
3040 }
3041 bool fLockReq = (it2->GetLockRequest() || it->GetLockRequest());
3042 childLock.release();
3043 alock.release();
3044 rc = it->UpdateLock(fLockReq);
3045 alock.acquire();
3046 childLock.acquire();
3047 if (FAILED(rc))
3048 {
3049 // could not update the lock, trigger cleanup below
3050 fOnlineMergePossible = false;
3051 break;
3052 }
3053 }
3054
3055 if (fOnlineMergePossible)
3056 {
3057 /* we will lock the children of the source for reparenting */
3058 for (MediaList::const_iterator it = aChildrenToReparent.begin();
3059 it != aChildrenToReparent.end();
3060 ++it)
3061 {
3062 ComObjPtr<Medium> pMedium = *it;
3063 AutoReadLock mediumLock(pMedium COMMA_LOCKVAL_SRC_POS);
3064 if (pMedium->getState() == MediumState_Created)
3065 {
3066 mediumLock.release();
3067 childLock.release();
3068 alock.release();
3069 rc = pMedium->LockWrite(NULL);
3070 alock.acquire();
3071 childLock.acquire();
3072 mediumLock.acquire();
3073 if (FAILED(rc))
3074 throw rc;
3075 }
3076 else
3077 {
3078 mediumLock.release();
3079 childLock.release();
3080 alock.release();
3081 rc = aVMMALockList->Update(pMedium, true);
3082 alock.acquire();
3083 childLock.acquire();
3084 mediumLock.acquire();
3085 if (FAILED(rc))
3086 {
3087 mediumLock.release();
3088 childLock.release();
3089 alock.release();
3090 rc = pMedium->LockWrite(NULL);
3091 alock.acquire();
3092 childLock.acquire();
3093 mediumLock.acquire();
3094 if (FAILED(rc))
3095 throw rc;
3096 }
3097 }
3098 }
3099 }
3100
3101 if (fOnlineMergePossible)
3102 {
3103 childLock.release();
3104 alock.release();
3105 rc = aVMMALockList->Lock();
3106 alock.acquire();
3107 childLock.acquire();
3108 if (FAILED(rc))
3109 {
3110 aSource->cancelMergeTo(aChildrenToReparent, aMediumLockList);
3111 rc = setError(rc,
3112 tr("Cannot lock hard disk '%s' for a live merge"),
3113 aHD->getLocationFull().c_str());
3114 }
3115 else
3116 {
3117 delete aMediumLockList;
3118 aMediumLockList = aVMMALockList;
3119 fNeedsOnlineMerge = true;
3120 }
3121 }
3122 else
3123 {
3124 aSource->cancelMergeTo(aChildrenToReparent, aMediumLockList);
3125 rc = setError(rc,
3126 tr("Failed to construct lock list for a live merge of hard disk '%s'"),
3127 aHD->getLocationFull().c_str());
3128 }
3129
3130 // fix the VM's lock list if anything failed
3131 if (FAILED(rc))
3132 {
3133 lockListVMMABegin = aVMMALockList->GetBegin();
3134 lockListVMMAEnd = aVMMALockList->GetEnd();
3135 MediumLockList::Base::iterator lockListLast = lockListVMMAEnd;
3136 lockListLast--;
3137 for (MediumLockList::Base::iterator it = lockListVMMABegin;
3138 it != lockListVMMAEnd;
3139 ++it)
3140 {
3141 childLock.release();
3142 alock.release();
3143 it->UpdateLock(it == lockListLast);
3144 alock.acquire();
3145 childLock.acquire();
3146 ComObjPtr<Medium> pMedium = it->GetMedium();
3147 AutoWriteLock mediumLock(pMedium COMMA_LOCKVAL_SRC_POS);
3148 // blindly apply this, only needed for medium objects which
3149 // would be deleted as part of the merge
3150 pMedium->unmarkLockedForDeletion();
3151 }
3152 }
3153
3154 }
3155 else
3156 {
3157 aSource->cancelMergeTo(aChildrenToReparent, aMediumLockList);
3158 rc = setError(rc,
3159 tr("Cannot lock hard disk '%s' for an offline merge"),
3160 aHD->getLocationFull().c_str());
3161 }
3162 }
3163
3164 return rc;
3165}
3166
3167/**
3168 * Cancels the deletion/merging of this hard disk (part of a snapshot). Undoes
3169 * what #prepareDeleteSnapshotMedium() did. Must be called if
3170 * #deleteSnapshotMedium() is not called or fails.
3171 *
3172 * @param aHD Hard disk which is connected to the snapshot.
3173 * @param aSource Source hard disk for merge.
3174 * @param aChildrenToReparent Children to unlock.
3175 * @param fNeedsOnlineMerge Whether this merge needs to be done online.
3176 * @param aMediumLockList Medium locks to cancel.
3177 * @param aMachineId Machine id to attach the medium to.
3178 * @param aSnapshotId Snapshot id to attach the medium to.
3179 *
3180 * @note Locks the medium tree and the hard disks in the chain for writing.
3181 */
3182void SessionMachine::cancelDeleteSnapshotMedium(const ComObjPtr<Medium> &aHD,
3183 const ComObjPtr<Medium> &aSource,
3184 const MediaList &aChildrenToReparent,
3185 bool fNeedsOnlineMerge,
3186 MediumLockList *aMediumLockList,
3187 const Guid &aMachineId,
3188 const Guid &aSnapshotId)
3189{
3190 if (aMediumLockList == NULL)
3191 {
3192 AutoMultiWriteLock2 mLock(&mParent->getMediaTreeLockHandle(), aHD->lockHandle() COMMA_LOCKVAL_SRC_POS);
3193
3194 Assert(aHD->getChildren().size() == 0);
3195
3196 if (aHD->getParent().isNull())
3197 {
3198 HRESULT rc = aHD->UnlockWrite(NULL);
3199 AssertComRC(rc);
3200 }
3201 else
3202 {
3203 HRESULT rc = aHD->unmarkForDeletion();
3204 AssertComRC(rc);
3205 }
3206 }
3207 else
3208 {
3209 if (fNeedsOnlineMerge)
3210 {
3211 // Online merge uses the medium lock list of the VM, so give
3212 // an empty list to cancelMergeTo so that it works as designed.
3213 aSource->cancelMergeTo(aChildrenToReparent, new MediumLockList());
3214
3215 // clean up the VM medium lock list ourselves
3216 MediumLockList::Base::iterator lockListBegin =
3217 aMediumLockList->GetBegin();
3218 MediumLockList::Base::iterator lockListEnd =
3219 aMediumLockList->GetEnd();
3220 MediumLockList::Base::iterator lockListLast = lockListEnd;
3221 lockListLast--;
3222 for (MediumLockList::Base::iterator it = lockListBegin;
3223 it != lockListEnd;
3224 ++it)
3225 {
3226 ComObjPtr<Medium> pMedium = it->GetMedium();
3227 AutoWriteLock mediumLock(pMedium COMMA_LOCKVAL_SRC_POS);
3228 if (pMedium->getState() == MediumState_Deleting)
3229 pMedium->unmarkForDeletion();
3230 else
3231 {
3232 // blindly apply this, only needed for medium objects which
3233 // would be deleted as part of the merge
3234 pMedium->unmarkLockedForDeletion();
3235 }
3236 mediumLock.release();
3237 it->UpdateLock(it == lockListLast);
3238 mediumLock.acquire();
3239 }
3240 }
3241 else
3242 {
3243 aSource->cancelMergeTo(aChildrenToReparent, aMediumLockList);
3244 }
3245 }
3246
3247 if (aMachineId.isValid() && !aMachineId.isZero())
3248 {
3249 // reattach the source media to the snapshot
3250 HRESULT rc = aSource->addBackReference(aMachineId, aSnapshotId);
3251 AssertComRC(rc);
3252 }
3253}
3254
3255/**
3256 * Perform an online merge of a hard disk, i.e. the equivalent of
3257 * Medium::mergeTo(), just for running VMs. If this fails you need to call
3258 * #cancelDeleteSnapshotMedium().
3259 *
3260 * @return COM status code
3261 * @param aMediumAttachment Identify where the disk is attached in the VM.
3262 * @param aSource Source hard disk for merge.
3263 * @param aTarget Target hard disk for merge.
3264 * @param aMergeForward Merge direction.
3265 * @param aParentForTarget New parent if target needs to be reparented.
3266 * @param aChildrenToReparent Children which have to be reparented to the
3267 * target.
3268 * @param aMediumLockList Where to store the created medium lock list (may
3269 * return NULL if no real merge is necessary).
3270 * @param aProgress Progress indicator.
3271 * @param pfNeedsMachineSaveSettings Whether the VM settings need to be saved (out).
3272 */
3273HRESULT SessionMachine::onlineMergeMedium(const ComObjPtr<MediumAttachment> &aMediumAttachment,
3274 const ComObjPtr<Medium> &aSource,
3275 const ComObjPtr<Medium> &aTarget,
3276 bool fMergeForward,
3277 const ComObjPtr<Medium> &aParentForTarget,
3278 const MediaList &aChildrenToReparent,
3279 MediumLockList *aMediumLockList,
3280 ComObjPtr<Progress> &aProgress,
3281 bool *pfNeedsMachineSaveSettings)
3282{
3283 AssertReturn(aSource != NULL, E_FAIL);
3284 AssertReturn(aTarget != NULL, E_FAIL);
3285 AssertReturn(aSource != aTarget, E_FAIL);
3286 AssertReturn(aMediumLockList != NULL, E_FAIL);
3287
3288 HRESULT rc = S_OK;
3289
3290 try
3291 {
3292 // Similar code appears in Medium::taskMergeHandle, so
3293 // if you make any changes below check whether they are applicable
3294 // in that context as well.
3295
3296 unsigned uTargetIdx = (unsigned)-1;
3297 unsigned uSourceIdx = (unsigned)-1;
3298 /* Sanity check all hard disks in the chain. */
3299 MediumLockList::Base::iterator lockListBegin =
3300 aMediumLockList->GetBegin();
3301 MediumLockList::Base::iterator lockListEnd =
3302 aMediumLockList->GetEnd();
3303 unsigned i = 0;
3304 for (MediumLockList::Base::iterator it = lockListBegin;
3305 it != lockListEnd;
3306 ++it)
3307 {
3308 MediumLock &mediumLock = *it;
3309 const ComObjPtr<Medium> &pMedium = mediumLock.GetMedium();
3310
3311 if (pMedium == aSource)
3312 uSourceIdx = i;
3313 else if (pMedium == aTarget)
3314 uTargetIdx = i;
3315
3316 // In Medium::taskMergeHandler there is lots of consistency
3317 // checking which we cannot do here, as the state details are
3318 // impossible to get outside the Medium class. The locking should
3319 // have done the checks already.
3320
3321 i++;
3322 }
3323
3324 ComAssertThrow( uSourceIdx != (unsigned)-1
3325 && uTargetIdx != (unsigned)-1, E_FAIL);
3326
3327 // For forward merges, tell the VM what images need to have their
3328 // parent UUID updated. This cannot be done in VBoxSVC, as opening
3329 // the required parent images is not safe while the VM is running.
3330 // For backward merges this will be simply an array of size 0.
3331 com::SafeIfaceArray<IMedium> childrenToReparent(aChildrenToReparent);
3332
3333 ComPtr<IInternalSessionControl> directControl;
3334 {
3335 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3336
3337 if (mData->mSession.mState != SessionState_Locked)
3338 throw setError(VBOX_E_INVALID_VM_STATE,
3339 tr("Machine is not locked by a session (session state: %s)"),
3340 Global::stringifySessionState(mData->mSession.mState));
3341 directControl = mData->mSession.mDirectControl;
3342 }
3343
3344 // Must not hold any locks here, as this will call back to finish
3345 // updating the medium attachment, chain linking and state.
3346 rc = directControl->OnlineMergeMedium(aMediumAttachment,
3347 uSourceIdx, uTargetIdx,
3348 aSource, aTarget,
3349 fMergeForward, aParentForTarget,
3350 ComSafeArrayAsInParam(childrenToReparent),
3351 aProgress);
3352 if (FAILED(rc))
3353 throw rc;
3354 }
3355 catch (HRESULT aRC) { rc = aRC; }
3356
3357 // The callback mentioned above takes care of update the medium state
3358
3359 if (pfNeedsMachineSaveSettings)
3360 *pfNeedsMachineSaveSettings = true;
3361
3362 return rc;
3363}
3364
3365/**
3366 * Implementation for IInternalMachineControl::finishOnlineMergeMedium().
3367 *
3368 * Gets called after the successful completion of an online merge from
3369 * Console::onlineMergeMedium(), which gets invoked indirectly above in
3370 * the call to IInternalSessionControl::onlineMergeMedium.
3371 *
3372 * This updates the medium information and medium state so that the VM
3373 * can continue with the updated state of the medium chain.
3374 */
3375STDMETHODIMP SessionMachine::FinishOnlineMergeMedium(IMediumAttachment *aMediumAttachment,
3376 IMedium *aSource,
3377 IMedium *aTarget,
3378 BOOL aMergeForward,
3379 IMedium *aParentForTarget,
3380 ComSafeArrayIn(IMedium *, aChildrenToReparent))
3381{
3382 HRESULT rc = S_OK;
3383 ComObjPtr<Medium> pSource(static_cast<Medium *>(aSource));
3384 ComObjPtr<Medium> pTarget(static_cast<Medium *>(aTarget));
3385 ComObjPtr<Medium> pParentForTarget(static_cast<Medium *>(aParentForTarget));
3386 bool fSourceHasChildren = false;
3387
3388 // all hard disks but the target were successfully deleted by
3389 // the merge; reparent target if necessary and uninitialize media
3390
3391 AutoWriteLock treeLock(mParent->getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
3392
3393 // Declare this here to make sure the object does not get uninitialized
3394 // before this method completes. Would normally happen as halfway through
3395 // we delete the last reference to the no longer existing medium object.
3396 ComObjPtr<Medium> targetChild;
3397
3398 if (aMergeForward)
3399 {
3400 // first, unregister the target since it may become a base
3401 // hard disk which needs re-registration
3402 rc = mParent->unregisterMedium(pTarget);
3403 AssertComRC(rc);
3404
3405 // then, reparent it and disconnect the deleted branch at
3406 // both ends (chain->parent() is source's parent)
3407 pTarget->deparent();
3408 pTarget->setParent(pParentForTarget);
3409 if (pParentForTarget)
3410 pSource->deparent();
3411
3412 // then, register again
3413 rc = mParent->registerMedium(pTarget, &pTarget, DeviceType_HardDisk);
3414 AssertComRC(rc);
3415 }
3416 else
3417 {
3418 Assert(pTarget->getChildren().size() == 1);
3419 targetChild = pTarget->getChildren().front();
3420
3421 // disconnect the deleted branch at the elder end
3422 targetChild->deparent();
3423
3424 // Update parent UUIDs of the source's children, reparent them and
3425 // disconnect the deleted branch at the younger end
3426 com::SafeIfaceArray<IMedium> childrenToReparent(ComSafeArrayInArg(aChildrenToReparent));
3427 if (childrenToReparent.size() > 0)
3428 {
3429 fSourceHasChildren = true;
3430 // Fix the parent UUID of the images which needs to be moved to
3431 // underneath target. The running machine has the images opened,
3432 // but only for reading since the VM is paused. If anything fails
3433 // we must continue. The worst possible result is that the images
3434 // need manual fixing via VBoxManage to adjust the parent UUID.
3435 MediaList toReparent;
3436 for (size_t i = 0; i < childrenToReparent.size(); i++)
3437 {
3438 Medium *pMedium = static_cast<Medium *>(childrenToReparent[i]);
3439 toReparent.push_back(pMedium);
3440 }
3441 treeLock.release();
3442 pTarget->fixParentUuidOfChildren(toReparent);
3443 treeLock.acquire();
3444
3445 // obey {parent,child} lock order
3446 AutoWriteLock sourceLock(pSource COMMA_LOCKVAL_SRC_POS);
3447
3448 for (size_t i = 0; i < childrenToReparent.size(); i++)
3449 {
3450 Medium *pMedium = static_cast<Medium *>(childrenToReparent[i]);
3451 AutoWriteLock childLock(pMedium COMMA_LOCKVAL_SRC_POS);
3452
3453 pMedium->deparent(); // removes pMedium from source
3454 pMedium->setParent(pTarget);
3455 }
3456 }
3457 }
3458
3459 /* unregister and uninitialize all hard disks removed by the merge */
3460 MediumLockList *pMediumLockList = NULL;
3461 MediumAttachment *pMediumAttachment = static_cast<MediumAttachment *>(aMediumAttachment);
3462 rc = mData->mSession.mLockedMedia.Get(pMediumAttachment, pMediumLockList);
3463 const ComObjPtr<Medium> &pLast = aMergeForward ? pTarget : pSource;
3464 AssertReturn(SUCCEEDED(rc) && pMediumLockList, E_FAIL);
3465 MediumLockList::Base::iterator lockListBegin =
3466 pMediumLockList->GetBegin();
3467 MediumLockList::Base::iterator lockListEnd =
3468 pMediumLockList->GetEnd();
3469 for (MediumLockList::Base::iterator it = lockListBegin;
3470 it != lockListEnd;
3471 )
3472 {
3473 MediumLock &mediumLock = *it;
3474 /* Create a real copy of the medium pointer, as the medium
3475 * lock deletion below would invalidate the referenced object. */
3476 const ComObjPtr<Medium> pMedium = mediumLock.GetMedium();
3477
3478 /* The target and all images not merged (readonly) are skipped */
3479 if ( pMedium == pTarget
3480 || pMedium->getState() == MediumState_LockedRead)
3481 {
3482 ++it;
3483 }
3484 else
3485 {
3486 rc = mParent->unregisterMedium(pMedium);
3487 AssertComRC(rc);
3488
3489 /* now, uninitialize the deleted hard disk (note that
3490 * due to the Deleting state, uninit() will not touch
3491 * the parent-child relationship so we need to
3492 * uninitialize each disk individually) */
3493
3494 /* note that the operation initiator hard disk (which is
3495 * normally also the source hard disk) is a special case
3496 * -- there is one more caller added by Task to it which
3497 * we must release. Also, if we are in sync mode, the
3498 * caller may still hold an AutoCaller instance for it
3499 * and therefore we cannot uninit() it (it's therefore
3500 * the caller's responsibility) */
3501 if (pMedium == aSource)
3502 {
3503 Assert(pSource->getChildren().size() == 0);
3504 Assert(pSource->getFirstMachineBackrefId() == NULL);
3505 }
3506
3507 /* Delete the medium lock list entry, which also releases the
3508 * caller added by MergeChain before uninit() and updates the
3509 * iterator to point to the right place. */
3510 rc = pMediumLockList->RemoveByIterator(it);
3511 AssertComRC(rc);
3512
3513 pMedium->uninit();
3514 }
3515
3516 /* Stop as soon as we reached the last medium affected by the merge.
3517 * The remaining images must be kept unchanged. */
3518 if (pMedium == pLast)
3519 break;
3520 }
3521
3522 /* Could be in principle folded into the previous loop, but let's keep
3523 * things simple. Update the medium locking to be the standard state:
3524 * all parent images locked for reading, just the last diff for writing. */
3525 lockListBegin = pMediumLockList->GetBegin();
3526 lockListEnd = pMediumLockList->GetEnd();
3527 MediumLockList::Base::iterator lockListLast = lockListEnd;
3528 lockListLast--;
3529 for (MediumLockList::Base::iterator it = lockListBegin;
3530 it != lockListEnd;
3531 ++it)
3532 {
3533 it->UpdateLock(it == lockListLast);
3534 }
3535
3536 /* If this is a backwards merge of the only remaining snapshot (i.e. the
3537 * source has no children) then update the medium associated with the
3538 * attachment, as the previously associated one (source) is now deleted.
3539 * Without the immediate update the VM could not continue running. */
3540 if (!aMergeForward && !fSourceHasChildren)
3541 {
3542 AutoWriteLock attLock(pMediumAttachment COMMA_LOCKVAL_SRC_POS);
3543 pMediumAttachment->updateMedium(pTarget);
3544 }
3545
3546 return S_OK;
3547}
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