VirtualBox

source: vbox/trunk/src/VBox/Main/SnapshotImpl.cpp@ 25414

Last change on this file since 25414 was 25310, checked in by vboxsync, 14 years ago

Main: lock validator, first batch: implement per-thread stack to trace locking (disabled by default, use VBOX_WITH_LOCK_VALIDATOR, but that WILL FAIL presently)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 79.0 KB
Line 
1/** @file
2 *
3 * COM class implementation for Snapshot and SnapshotMachine.
4 */
5
6/*
7 * Copyright (C) 2006-2007 Sun Microsystems, Inc.
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
18 * Clara, CA 95054 USA or visit http://www.sun.com if you need
19 * additional information or have any questions.
20 */
21
22#include "SnapshotImpl.h"
23
24#include "MachineImpl.h"
25#include "MediumImpl.h"
26#include "Global.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 "Logging.h"
35
36#include <iprt/path.h>
37#include <VBox/param.h>
38#include <VBox/err.h>
39
40#include <VBox/settings.h>
41
42////////////////////////////////////////////////////////////////////////////////
43//
44// Globals
45//
46////////////////////////////////////////////////////////////////////////////////
47
48/**
49 * Progress callback handler for lengthy operations
50 * (corresponds to the FNRTPROGRESS typedef).
51 *
52 * @param uPercentage Completetion precentage (0-100).
53 * @param pvUser Pointer to the Progress instance.
54 */
55static DECLCALLBACK(int) progressCallback(unsigned uPercentage, void *pvUser)
56{
57 IProgress *progress = static_cast<IProgress*>(pvUser);
58
59 /* update the progress object */
60 if (progress)
61 progress->SetCurrentOperationProgress(uPercentage);
62
63 return VINF_SUCCESS;
64}
65
66////////////////////////////////////////////////////////////////////////////////
67//
68// Snapshot private data definition
69//
70////////////////////////////////////////////////////////////////////////////////
71
72typedef std::list< ComObjPtr<Snapshot> > SnapshotsList;
73
74struct Snapshot::Data
75{
76 Data()
77 {
78 RTTimeSpecSetMilli(&timeStamp, 0);
79 };
80
81 ~Data()
82 {}
83
84 Guid uuid;
85 Utf8Str strName;
86 Utf8Str strDescription;
87 RTTIMESPEC timeStamp;
88 ComObjPtr<SnapshotMachine> pMachine;
89
90 /** weak VirtualBox parent */
91 const ComObjPtr<VirtualBox, ComWeakRef> pVirtualBox;
92
93 // pParent and llChildren are protected by Machine::snapshotsTreeLockHandle()
94 ComObjPtr<Snapshot> pParent;
95 SnapshotsList llChildren;
96};
97
98////////////////////////////////////////////////////////////////////////////////
99//
100// Constructor / destructor
101//
102////////////////////////////////////////////////////////////////////////////////
103
104HRESULT Snapshot::FinalConstruct()
105{
106 LogFlowMember (("Snapshot::FinalConstruct()\n"));
107 return S_OK;
108}
109
110void Snapshot::FinalRelease()
111{
112 LogFlowMember (("Snapshot::FinalRelease()\n"));
113 uninit();
114}
115
116/**
117 * Initializes the instance
118 *
119 * @param aId id of the snapshot
120 * @param aName name of the snapshot
121 * @param aDescription name of the snapshot (NULL if no description)
122 * @param aTimeStamp timestamp of the snapshot, in ms since 1970-01-01 UTC
123 * @param aMachine machine associated with this snapshot
124 * @param aParent parent snapshot (NULL if no parent)
125 */
126HRESULT Snapshot::init(VirtualBox *aVirtualBox,
127 const Guid &aId,
128 const Utf8Str &aName,
129 const Utf8Str &aDescription,
130 const RTTIMESPEC &aTimeStamp,
131 SnapshotMachine *aMachine,
132 Snapshot *aParent)
133{
134 LogFlowMember(("Snapshot::init(uuid: %s, aParent->uuid=%s)\n", aId.toString().c_str(), (aParent) ? aParent->m->uuid.toString().c_str() : ""));
135
136 ComAssertRet (!aId.isEmpty() && !aName.isEmpty() && aMachine, E_INVALIDARG);
137
138 /* Enclose the state transition NotReady->InInit->Ready */
139 AutoInitSpan autoInitSpan(this);
140 AssertReturn(autoInitSpan.isOk(), E_FAIL);
141
142 m = new Data;
143
144 /* share parent weakly */
145 unconst(m->pVirtualBox) = aVirtualBox;
146
147 m->pParent = aParent;
148
149 m->uuid = aId;
150 m->strName = aName;
151 m->strDescription = aDescription;
152 m->timeStamp = aTimeStamp;
153 m->pMachine = aMachine;
154
155 if (aParent)
156 aParent->m->llChildren.push_back(this);
157
158 /* Confirm a successful initialization when it's the case */
159 autoInitSpan.setSucceeded();
160
161 return S_OK;
162}
163
164/**
165 * Uninitializes the instance and sets the ready flag to FALSE.
166 * Called either from FinalRelease(), by the parent when it gets destroyed,
167 * or by a third party when it decides this object is no more valid.
168 */
169void Snapshot::uninit()
170{
171 LogFlowMember (("Snapshot::uninit()\n"));
172
173 /* Enclose the state transition Ready->InUninit->NotReady */
174 AutoUninitSpan autoUninitSpan(this);
175 if (autoUninitSpan.uninitDone())
176 return;
177
178 // uninit all children
179 SnapshotsList::iterator it;
180 for (it = m->llChildren.begin();
181 it != m->llChildren.end();
182 ++it)
183 {
184 Snapshot *pChild = *it;
185 pChild->m->pParent.setNull();
186 pChild->uninit();
187 }
188 m->llChildren.clear(); // this unsets all the ComPtrs and probably calls delete
189
190 if (m->pParent)
191 deparent();
192
193 if (m->pMachine)
194 {
195 m->pMachine->uninit();
196 m->pMachine.setNull();
197 }
198
199 delete m;
200 m = NULL;
201}
202
203/**
204 * Discards the current snapshot by removing it from the tree of snapshots
205 * and reparenting its children.
206 *
207 * After this, the caller must call uninit() on the snapshot. We can't call
208 * that from here because if we do, the AutoUninitSpan waits forever for
209 * the number of callers to become 0 (it is 1 because of the AutoCaller in here).
210 *
211 * NOTE: this does NOT lock the snapshot, it is assumed that the caller has
212 * locked a) the machine and b) the snapshots tree in write mode!
213 */
214void Snapshot::beginDiscard()
215{
216 AutoCaller autoCaller(this);
217 if (FAILED(autoCaller.rc()))
218 return;
219
220 /* for now, the snapshot must have only one child when discarded,
221 * or no children at all */
222 AssertReturnVoid(m->llChildren.size() <= 1);
223
224 ComObjPtr<Snapshot> parentSnapshot = m->pParent;
225
226 /// @todo (dmik):
227 // when we introduce clones later, discarding the snapshot
228 // will affect the current and first snapshots of clones, if they are
229 // direct children of this snapshot. So we will need to lock machines
230 // associated with child snapshots as well and update mCurrentSnapshot
231 // and/or mFirstSnapshot fields.
232
233 if (this == m->pMachine->mData->mCurrentSnapshot)
234 {
235 m->pMachine->mData->mCurrentSnapshot = parentSnapshot;
236
237 /* we've changed the base of the current state so mark it as
238 * modified as it no longer guaranteed to be its copy */
239 m->pMachine->mData->mCurrentStateModified = TRUE;
240 }
241
242 if (this == m->pMachine->mData->mFirstSnapshot)
243 {
244 if (m->llChildren.size() == 1)
245 {
246 ComObjPtr<Snapshot> childSnapshot = m->llChildren.front();
247 m->pMachine->mData->mFirstSnapshot = childSnapshot;
248 }
249 else
250 m->pMachine->mData->mFirstSnapshot.setNull();
251 }
252
253 // reparent our children
254 for (SnapshotsList::const_iterator it = m->llChildren.begin();
255 it != m->llChildren.end();
256 ++it)
257 {
258 ComObjPtr<Snapshot> child = *it;
259 AutoWriteLock childLock(child COMMA_LOCKVAL_SRC_POS);
260
261 child->m->pParent = m->pParent;
262 if (m->pParent)
263 m->pParent->m->llChildren.push_back(child);
264 }
265
266 // clear our own children list (since we reparented the children)
267 m->llChildren.clear();
268}
269
270/**
271 * Internal helper that removes "this" from the list of children of its
272 * parent. Used in uninit() and other places when reparenting is necessary.
273 *
274 * The caller must hold the snapshots tree lock!
275 */
276void Snapshot::deparent()
277{
278 SnapshotsList &llParent = m->pParent->m->llChildren;
279 for (SnapshotsList::iterator it = llParent.begin();
280 it != llParent.end();
281 ++it)
282 {
283 Snapshot *pParentsChild = *it;
284 if (this == pParentsChild)
285 {
286 llParent.erase(it);
287 break;
288 }
289 }
290
291 m->pParent.setNull();
292}
293
294////////////////////////////////////////////////////////////////////////////////
295//
296// ISnapshot public methods
297//
298////////////////////////////////////////////////////////////////////////////////
299
300STDMETHODIMP Snapshot::COMGETTER(Id) (BSTR *aId)
301{
302 CheckComArgOutPointerValid(aId);
303
304 AutoCaller autoCaller(this);
305 if (FAILED(autoCaller.rc())) return autoCaller.rc();
306
307 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
308
309 m->uuid.toUtf16().cloneTo(aId);
310 return S_OK;
311}
312
313STDMETHODIMP Snapshot::COMGETTER(Name) (BSTR *aName)
314{
315 CheckComArgOutPointerValid(aName);
316
317 AutoCaller autoCaller(this);
318 if (FAILED(autoCaller.rc())) return autoCaller.rc();
319
320 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
321
322 m->strName.cloneTo(aName);
323 return S_OK;
324}
325
326/**
327 * @note Locks this object for writing, then calls Machine::onSnapshotChange()
328 * (see its lock requirements).
329 */
330STDMETHODIMP Snapshot::COMSETTER(Name)(IN_BSTR aName)
331{
332 CheckComArgNotNull(aName);
333
334 AutoCaller autoCaller(this);
335 if (FAILED(autoCaller.rc())) return autoCaller.rc();
336
337 Utf8Str strName(aName);
338
339 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
340
341 if (m->strName != strName)
342 {
343 m->strName = strName;
344
345 alock.leave(); /* Important! (child->parent locks are forbidden) */
346
347 return m->pMachine->onSnapshotChange(this);
348 }
349
350 return S_OK;
351}
352
353STDMETHODIMP Snapshot::COMGETTER(Description) (BSTR *aDescription)
354{
355 CheckComArgOutPointerValid(aDescription);
356
357 AutoCaller autoCaller(this);
358 if (FAILED(autoCaller.rc())) return autoCaller.rc();
359
360 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
361
362 m->strDescription.cloneTo(aDescription);
363 return S_OK;
364}
365
366STDMETHODIMP Snapshot::COMSETTER(Description) (IN_BSTR aDescription)
367{
368 CheckComArgNotNull(aDescription);
369
370 AutoCaller autoCaller(this);
371 if (FAILED(autoCaller.rc())) return autoCaller.rc();
372
373 Utf8Str strDescription(aDescription);
374
375 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
376
377 if (m->strDescription != strDescription)
378 {
379 m->strDescription = strDescription;
380
381 alock.leave(); /* Important! (child->parent locks are forbidden) */
382
383 return m->pMachine->onSnapshotChange(this);
384 }
385
386 return S_OK;
387}
388
389STDMETHODIMP Snapshot::COMGETTER(TimeStamp) (LONG64 *aTimeStamp)
390{
391 CheckComArgOutPointerValid(aTimeStamp);
392
393 AutoCaller autoCaller(this);
394 if (FAILED(autoCaller.rc())) return autoCaller.rc();
395
396 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
397
398 *aTimeStamp = RTTimeSpecGetMilli(&m->timeStamp);
399 return S_OK;
400}
401
402STDMETHODIMP Snapshot::COMGETTER(Online)(BOOL *aOnline)
403{
404 CheckComArgOutPointerValid(aOnline);
405
406 AutoCaller autoCaller(this);
407 if (FAILED(autoCaller.rc())) return autoCaller.rc();
408
409 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
410
411 *aOnline = !stateFilePath().isEmpty();
412 return S_OK;
413}
414
415STDMETHODIMP Snapshot::COMGETTER(Machine) (IMachine **aMachine)
416{
417 CheckComArgOutPointerValid(aMachine);
418
419 AutoCaller autoCaller(this);
420 if (FAILED(autoCaller.rc())) return autoCaller.rc();
421
422 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
423
424 m->pMachine.queryInterfaceTo(aMachine);
425 return S_OK;
426}
427
428STDMETHODIMP Snapshot::COMGETTER(Parent) (ISnapshot **aParent)
429{
430 CheckComArgOutPointerValid(aParent);
431
432 AutoCaller autoCaller(this);
433 if (FAILED(autoCaller.rc())) return autoCaller.rc();
434
435 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
436
437 m->pParent.queryInterfaceTo(aParent);
438 return S_OK;
439}
440
441STDMETHODIMP Snapshot::COMGETTER(Children) (ComSafeArrayOut(ISnapshot *, aChildren))
442{
443 CheckComArgOutSafeArrayPointerValid(aChildren);
444
445 AutoCaller autoCaller(this);
446 if (FAILED(autoCaller.rc())) return autoCaller.rc();
447
448 AutoReadLock alock(m->pMachine->snapshotsTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
449 AutoReadLock block(this->lockHandle() COMMA_LOCKVAL_SRC_POS);
450
451 SafeIfaceArray<ISnapshot> collection(m->llChildren);
452 collection.detachTo(ComSafeArrayOutArg(aChildren));
453
454 return S_OK;
455}
456
457////////////////////////////////////////////////////////////////////////////////
458//
459// Snapshot public internal methods
460//
461////////////////////////////////////////////////////////////////////////////////
462
463/**
464 * Returns the parent snapshot or NULL if there's none. Must have caller + locking!
465 * @return
466 */
467const ComObjPtr<Snapshot>& Snapshot::getParent() const
468{
469 return m->pParent;
470}
471
472/**
473 * @note
474 * Must be called from under the object's lock!
475 */
476const Utf8Str& Snapshot::stateFilePath() const
477{
478 return m->pMachine->mSSData->mStateFilePath;
479}
480
481/**
482 * Returns the number of direct child snapshots, without grandchildren.
483 * Does not recurse.
484 * @return
485 */
486ULONG Snapshot::getChildrenCount()
487{
488 AutoCaller autoCaller(this);
489 AssertComRC(autoCaller.rc());
490
491 AutoReadLock treeLock(m->pMachine->snapshotsTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
492 return (ULONG)m->llChildren.size();
493}
494
495/**
496 * Implementation method for getAllChildrenCount() so we request the
497 * tree lock only once before recursing. Don't call directly.
498 * @return
499 */
500ULONG Snapshot::getAllChildrenCountImpl()
501{
502 AutoCaller autoCaller(this);
503 AssertComRC(autoCaller.rc());
504
505 ULONG count = (ULONG)m->llChildren.size();
506 for (SnapshotsList::const_iterator it = m->llChildren.begin();
507 it != m->llChildren.end();
508 ++it)
509 {
510 count += (*it)->getAllChildrenCountImpl();
511 }
512
513 return count;
514}
515
516/**
517 * Returns the number of child snapshots including all grandchildren.
518 * Recurses into the snapshots tree.
519 * @return
520 */
521ULONG Snapshot::getAllChildrenCount()
522{
523 AutoCaller autoCaller(this);
524 AssertComRC(autoCaller.rc());
525
526 AutoReadLock treeLock(m->pMachine->snapshotsTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
527 return getAllChildrenCountImpl();
528}
529
530/**
531 * Returns the SnapshotMachine that this snapshot belongs to.
532 * Caller must hold the snapshot's object lock!
533 * @return
534 */
535const ComObjPtr<SnapshotMachine>& Snapshot::getSnapshotMachine() const
536{
537 return m->pMachine;
538}
539
540/**
541 * Returns the UUID of this snapshot.
542 * Caller must hold the snapshot's object lock!
543 * @return
544 */
545Guid Snapshot::getId() const
546{
547 return m->uuid;
548}
549
550/**
551 * Returns the name of this snapshot.
552 * Caller must hold the snapshot's object lock!
553 * @return
554 */
555const Utf8Str& Snapshot::getName() const
556{
557 return m->strName;
558}
559
560/**
561 * Returns the time stamp of this snapshot.
562 * Caller must hold the snapshot's object lock!
563 * @return
564 */
565RTTIMESPEC Snapshot::getTimeStamp() const
566{
567 return m->timeStamp;
568}
569
570/**
571 * Searches for a snapshot with the given ID among children, grand-children,
572 * etc. of this snapshot. This snapshot itself is also included in the search.
573 * Caller must hold the snapshots tree lock!
574 */
575ComObjPtr<Snapshot> Snapshot::findChildOrSelf(IN_GUID aId)
576{
577 ComObjPtr<Snapshot> child;
578
579 AutoCaller autoCaller(this);
580 AssertComRC(autoCaller.rc());
581
582 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
583
584 if (m->uuid == aId)
585 child = this;
586 else
587 {
588 alock.release();
589 for (SnapshotsList::const_iterator it = m->llChildren.begin();
590 it != m->llChildren.end();
591 ++it)
592 {
593 if ((child = (*it)->findChildOrSelf(aId)))
594 break;
595 }
596 }
597
598 return child;
599}
600
601/**
602 * Searches for a first snapshot with the given name among children,
603 * grand-children, etc. of this snapshot. This snapshot itself is also included
604 * in the search.
605 * Caller must hold the snapshots tree lock!
606 */
607ComObjPtr<Snapshot> Snapshot::findChildOrSelf(const Utf8Str &aName)
608{
609 ComObjPtr<Snapshot> child;
610 AssertReturn(!aName.isEmpty(), child);
611
612 AutoCaller autoCaller(this);
613 AssertComRC(autoCaller.rc());
614
615 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
616
617 if (m->strName == aName)
618 child = this;
619 else
620 {
621 alock.release();
622 for (SnapshotsList::const_iterator it = m->llChildren.begin();
623 it != m->llChildren.end();
624 ++it)
625 {
626 if ((child = (*it)->findChildOrSelf(aName)))
627 break;
628 }
629 }
630
631 return child;
632}
633
634/**
635 * Internal implementation for Snapshot::updateSavedStatePaths (below).
636 * @param aOldPath
637 * @param aNewPath
638 */
639void Snapshot::updateSavedStatePathsImpl(const char *aOldPath, const char *aNewPath)
640{
641 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
642
643 const Utf8Str &path = m->pMachine->mSSData->mStateFilePath;
644 LogFlowThisFunc(("Snap[%s].statePath={%s}\n", m->strName.c_str(), path.c_str()));
645
646 /* state file may be NULL (for offline snapshots) */
647 if ( path.length()
648 && RTPathStartsWith(path.c_str(), aOldPath)
649 )
650 {
651 m->pMachine->mSSData->mStateFilePath = Utf8StrFmt("%s%s", aNewPath, path.raw() + strlen(aOldPath));
652
653 LogFlowThisFunc(("-> updated: {%s}\n", path.raw()));
654 }
655
656 for (SnapshotsList::const_iterator it = m->llChildren.begin();
657 it != m->llChildren.end();
658 ++it)
659 {
660 Snapshot *pChild = *it;
661 pChild->updateSavedStatePathsImpl(aOldPath, aNewPath);
662 }
663}
664
665/**
666 * Checks if the specified path change affects the saved state file path of
667 * this snapshot or any of its (grand-)children and updates it accordingly.
668 *
669 * Intended to be called by Machine::openConfigLoader() only.
670 *
671 * @param aOldPath old path (full)
672 * @param aNewPath new path (full)
673 *
674 * @note Locks this object + children for writing.
675 */
676void Snapshot::updateSavedStatePaths(const char *aOldPath, const char *aNewPath)
677{
678 LogFlowThisFunc(("aOldPath={%s} aNewPath={%s}\n", aOldPath, aNewPath));
679
680 AssertReturnVoid(aOldPath);
681 AssertReturnVoid(aNewPath);
682
683 AutoCaller autoCaller(this);
684 AssertComRC(autoCaller.rc());
685
686 AutoWriteLock chLock(m->pMachine->snapshotsTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
687 // call the implementation under the tree lock
688 updateSavedStatePathsImpl(aOldPath, aNewPath);
689}
690
691/**
692 * Internal implementation for Snapshot::saveSnapshot (below).
693 * @param aNode
694 * @param aAttrsOnly
695 * @return
696 */
697HRESULT Snapshot::saveSnapshotImpl(settings::Snapshot &data, bool aAttrsOnly)
698{
699 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
700
701 data.uuid = m->uuid;
702 data.strName = m->strName;
703 data.timestamp = m->timeStamp;
704 data.strDescription = m->strDescription;
705
706 if (aAttrsOnly)
707 return S_OK;
708
709 /* stateFile (optional) */
710 if (!stateFilePath().isEmpty())
711 /* try to make the file name relative to the settings file dir */
712 m->pMachine->calculateRelativePath(stateFilePath(), data.strStateFile);
713 else
714 data.strStateFile.setNull();
715
716 HRESULT rc = m->pMachine->saveHardware(data.hardware);
717 if (FAILED(rc)) return rc;
718
719 rc = m->pMachine->saveStorageControllers(data.storage);
720 if (FAILED(rc)) return rc;
721
722 alock.release();
723
724 data.llChildSnapshots.clear();
725
726 if (m->llChildren.size())
727 {
728 for (SnapshotsList::const_iterator it = m->llChildren.begin();
729 it != m->llChildren.end();
730 ++it)
731 {
732 settings::Snapshot snap;
733 rc = (*it)->saveSnapshotImpl(snap, aAttrsOnly);
734 if (FAILED(rc)) return rc;
735
736 data.llChildSnapshots.push_back(snap);
737 }
738 }
739
740 return S_OK;
741}
742
743/**
744 * Saves the given snapshot and all its children (unless \a aAttrsOnly is true).
745 * It is assumed that the given node is empty (unless \a aAttrsOnly is true).
746 *
747 * @param aNode <Snapshot> node to save the snapshot to.
748 * @param aSnapshot Snapshot to save.
749 * @param aAttrsOnly If true, only updatge user-changeable attrs.
750 */
751HRESULT Snapshot::saveSnapshot(settings::Snapshot &data, bool aAttrsOnly)
752{
753 AutoWriteLock listLock(m->pMachine->snapshotsTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
754
755 return saveSnapshotImpl(data, aAttrsOnly);
756}
757
758////////////////////////////////////////////////////////////////////////////////
759//
760// SnapshotMachine implementation
761//
762////////////////////////////////////////////////////////////////////////////////
763
764DEFINE_EMPTY_CTOR_DTOR (SnapshotMachine)
765
766HRESULT SnapshotMachine::FinalConstruct()
767{
768 LogFlowThisFunc(("\n"));
769
770 /* set the proper type to indicate we're the SnapshotMachine instance */
771 unconst(mType) = IsSnapshotMachine;
772
773 return S_OK;
774}
775
776void SnapshotMachine::FinalRelease()
777{
778 LogFlowThisFunc(("\n"));
779
780 uninit();
781}
782
783/**
784 * Initializes the SnapshotMachine object when taking a snapshot.
785 *
786 * @param aSessionMachine machine to take a snapshot from
787 * @param aSnapshotId snapshot ID of this snapshot machine
788 * @param aStateFilePath file where the execution state will be later saved
789 * (or NULL for the offline snapshot)
790 *
791 * @note The aSessionMachine must be locked for writing.
792 */
793HRESULT SnapshotMachine::init(SessionMachine *aSessionMachine,
794 IN_GUID aSnapshotId,
795 const Utf8Str &aStateFilePath)
796{
797 LogFlowThisFuncEnter();
798 LogFlowThisFunc(("mName={%ls}\n", aSessionMachine->mUserData->mName.raw()));
799
800 AssertReturn(aSessionMachine && !Guid (aSnapshotId).isEmpty(), E_INVALIDARG);
801
802 /* Enclose the state transition NotReady->InInit->Ready */
803 AutoInitSpan autoInitSpan(this);
804 AssertReturn(autoInitSpan.isOk(), E_FAIL);
805
806 AssertReturn(aSessionMachine->isWriteLockOnCurrentThread(), E_FAIL);
807
808 mSnapshotId = aSnapshotId;
809
810 /* memorize the primary Machine instance (i.e. not SessionMachine!) */
811 unconst(mPeer) = aSessionMachine->mPeer;
812 /* share the parent pointer */
813 unconst(mParent) = mPeer->mParent;
814
815 /* take the pointer to Data to share */
816 mData.share (mPeer->mData);
817
818 /* take the pointer to UserData to share (our UserData must always be the
819 * same as Machine's data) */
820 mUserData.share (mPeer->mUserData);
821 /* make a private copy of all other data (recent changes from SessionMachine) */
822 mHWData.attachCopy (aSessionMachine->mHWData);
823 mMediaData.attachCopy(aSessionMachine->mMediaData);
824
825 /* SSData is always unique for SnapshotMachine */
826 mSSData.allocate();
827 mSSData->mStateFilePath = aStateFilePath;
828
829 HRESULT rc = S_OK;
830
831 /* create copies of all shared folders (mHWData after attiching a copy
832 * contains just references to original objects) */
833 for (HWData::SharedFolderList::iterator it = mHWData->mSharedFolders.begin();
834 it != mHWData->mSharedFolders.end();
835 ++it)
836 {
837 ComObjPtr<SharedFolder> folder;
838 folder.createObject();
839 rc = folder->initCopy (this, *it);
840 if (FAILED(rc)) return rc;
841 *it = folder;
842 }
843
844 /* associate hard disks with the snapshot
845 * (Machine::uninitDataAndChildObjects() will deassociate at destruction) */
846 for (MediaData::AttachmentList::const_iterator it = mMediaData->mAttachments.begin();
847 it != mMediaData->mAttachments.end();
848 ++it)
849 {
850 MediumAttachment *pAtt = *it;
851 Medium *pMedium = pAtt->getMedium();
852 if (pMedium) // can be NULL for non-harddisk
853 {
854 rc = pMedium->attachTo(mData->mUuid, mSnapshotId);
855 AssertComRC(rc);
856 }
857 }
858
859 /* create copies of all storage controllers (mStorageControllerData
860 * after attaching a copy contains just references to original objects) */
861 mStorageControllers.allocate();
862 for (StorageControllerList::const_iterator
863 it = aSessionMachine->mStorageControllers->begin();
864 it != aSessionMachine->mStorageControllers->end();
865 ++it)
866 {
867 ComObjPtr<StorageController> ctrl;
868 ctrl.createObject();
869 ctrl->initCopy (this, *it);
870 mStorageControllers->push_back(ctrl);
871 }
872
873 /* create all other child objects that will be immutable private copies */
874
875 unconst(mBIOSSettings).createObject();
876 mBIOSSettings->initCopy (this, mPeer->mBIOSSettings);
877
878#ifdef VBOX_WITH_VRDP
879 unconst(mVRDPServer).createObject();
880 mVRDPServer->initCopy (this, mPeer->mVRDPServer);
881#endif
882
883 unconst(mAudioAdapter).createObject();
884 mAudioAdapter->initCopy (this, mPeer->mAudioAdapter);
885
886 unconst(mUSBController).createObject();
887 mUSBController->init(this, mPeer->mUSBController);
888
889 for (ULONG slot = 0; slot < RT_ELEMENTS (mNetworkAdapters); slot++)
890 {
891 unconst(mNetworkAdapters[slot]).createObject();
892 mNetworkAdapters[slot]->initCopy (this, mPeer->mNetworkAdapters [slot]);
893 }
894
895 for (ULONG slot = 0; slot < RT_ELEMENTS (mSerialPorts); slot++)
896 {
897 unconst(mSerialPorts [slot]).createObject();
898 mSerialPorts[slot]->initCopy (this, mPeer->mSerialPorts[slot]);
899 }
900
901 for (ULONG slot = 0; slot < RT_ELEMENTS (mParallelPorts); slot++)
902 {
903 unconst(mParallelPorts[slot]).createObject();
904 mParallelPorts[slot]->initCopy (this, mPeer->mParallelPorts[slot]);
905 }
906
907 /* Confirm a successful initialization when it's the case */
908 autoInitSpan.setSucceeded();
909
910 LogFlowThisFuncLeave();
911 return S_OK;
912}
913
914/**
915 * Initializes the SnapshotMachine object when loading from the settings file.
916 *
917 * @param aMachine machine the snapshot belngs to
918 * @param aHWNode <Hardware> node
919 * @param aHDAsNode <HardDiskAttachments> node
920 * @param aSnapshotId snapshot ID of this snapshot machine
921 * @param aStateFilePath file where the execution state is saved
922 * (or NULL for the offline snapshot)
923 *
924 * @note Doesn't lock anything.
925 */
926HRESULT SnapshotMachine::init(Machine *aMachine,
927 const settings::Hardware &hardware,
928 const settings::Storage &storage,
929 IN_GUID aSnapshotId,
930 const Utf8Str &aStateFilePath)
931{
932 LogFlowThisFuncEnter();
933 LogFlowThisFunc(("mName={%ls}\n", aMachine->mUserData->mName.raw()));
934
935 AssertReturn(aMachine && !Guid(aSnapshotId).isEmpty(), E_INVALIDARG);
936
937 /* Enclose the state transition NotReady->InInit->Ready */
938 AutoInitSpan autoInitSpan(this);
939 AssertReturn(autoInitSpan.isOk(), E_FAIL);
940
941 /* Don't need to lock aMachine when VirtualBox is starting up */
942
943 mSnapshotId = aSnapshotId;
944
945 /* memorize the primary Machine instance */
946 unconst(mPeer) = aMachine;
947 /* share the parent pointer */
948 unconst(mParent) = mPeer->mParent;
949
950 /* take the pointer to Data to share */
951 mData.share (mPeer->mData);
952 /*
953 * take the pointer to UserData to share
954 * (our UserData must always be the same as Machine's data)
955 */
956 mUserData.share (mPeer->mUserData);
957 /* allocate private copies of all other data (will be loaded from settings) */
958 mHWData.allocate();
959 mMediaData.allocate();
960 mStorageControllers.allocate();
961
962 /* SSData is always unique for SnapshotMachine */
963 mSSData.allocate();
964 mSSData->mStateFilePath = aStateFilePath;
965
966 /* create all other child objects that will be immutable private copies */
967
968 unconst(mBIOSSettings).createObject();
969 mBIOSSettings->init (this);
970
971#ifdef VBOX_WITH_VRDP
972 unconst(mVRDPServer).createObject();
973 mVRDPServer->init (this);
974#endif
975
976 unconst(mAudioAdapter).createObject();
977 mAudioAdapter->init (this);
978
979 unconst(mUSBController).createObject();
980 mUSBController->init (this);
981
982 for (ULONG slot = 0; slot < RT_ELEMENTS (mNetworkAdapters); slot ++)
983 {
984 unconst(mNetworkAdapters [slot]).createObject();
985 mNetworkAdapters [slot]->init (this, slot);
986 }
987
988 for (ULONG slot = 0; slot < RT_ELEMENTS (mSerialPorts); slot ++)
989 {
990 unconst(mSerialPorts [slot]).createObject();
991 mSerialPorts [slot]->init (this, slot);
992 }
993
994 for (ULONG slot = 0; slot < RT_ELEMENTS (mParallelPorts); slot ++)
995 {
996 unconst(mParallelPorts [slot]).createObject();
997 mParallelPorts [slot]->init (this, slot);
998 }
999
1000 /* load hardware and harddisk settings */
1001
1002 HRESULT rc = loadHardware(hardware);
1003 if (SUCCEEDED(rc))
1004 rc = loadStorageControllers(storage, true /* aRegistered */, &mSnapshotId);
1005
1006 if (SUCCEEDED(rc))
1007 /* commit all changes made during the initialization */
1008 commit();
1009
1010 /* Confirm a successful initialization when it's the case */
1011 if (SUCCEEDED(rc))
1012 autoInitSpan.setSucceeded();
1013
1014 LogFlowThisFuncLeave();
1015 return rc;
1016}
1017
1018/**
1019 * Uninitializes this SnapshotMachine object.
1020 */
1021void SnapshotMachine::uninit()
1022{
1023 LogFlowThisFuncEnter();
1024
1025 /* Enclose the state transition Ready->InUninit->NotReady */
1026 AutoUninitSpan autoUninitSpan(this);
1027 if (autoUninitSpan.uninitDone())
1028 return;
1029
1030 uninitDataAndChildObjects();
1031
1032 /* free the essential data structure last */
1033 mData.free();
1034
1035 unconst(mParent).setNull();
1036 unconst(mPeer).setNull();
1037
1038 LogFlowThisFuncLeave();
1039}
1040
1041/**
1042 * Overrides VirtualBoxBase::lockHandle() in order to share the lock handle
1043 * with the primary Machine instance (mPeer).
1044 */
1045RWLockHandle *SnapshotMachine::lockHandle() const
1046{
1047 AssertReturn(!mPeer.isNull(), NULL);
1048 return mPeer->lockHandle();
1049}
1050
1051////////////////////////////////////////////////////////////////////////////////
1052//
1053// SnapshotMachine public internal methods
1054//
1055////////////////////////////////////////////////////////////////////////////////
1056
1057/**
1058 * Called by the snapshot object associated with this SnapshotMachine when
1059 * snapshot data such as name or description is changed.
1060 *
1061 * @note Locks this object for writing.
1062 */
1063HRESULT SnapshotMachine::onSnapshotChange (Snapshot *aSnapshot)
1064{
1065 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1066
1067 // mPeer->saveAllSnapshots(); @todo
1068
1069 /* inform callbacks */
1070 mParent->onSnapshotChange(mData->mUuid, aSnapshot->getId());
1071
1072 return S_OK;
1073}
1074
1075////////////////////////////////////////////////////////////////////////////////
1076//
1077// SessionMachine task records
1078//
1079////////////////////////////////////////////////////////////////////////////////
1080
1081/**
1082 * Abstract base class for SessionMachine::RestoreSnapshotTask and
1083 * SessionMachine::DeleteSnapshotTask. This is necessary since
1084 * RTThreadCreate cannot call a method as its thread function, so
1085 * instead we have it call the static SessionMachine::taskHandler,
1086 * which can then call the handler() method in here (implemented
1087 * by the children).
1088 */
1089struct SessionMachine::SnapshotTask
1090{
1091 SnapshotTask(SessionMachine *m,
1092 Progress *p,
1093 Snapshot *s)
1094 : pMachine(m),
1095 pProgress(p),
1096 machineStateBackup(m->mData->mMachineState), // save the current machine state
1097 pSnapshot(s)
1098 {}
1099
1100 void modifyBackedUpState(MachineState_T s)
1101 {
1102 *const_cast<MachineState_T*>(&machineStateBackup) = s;
1103 }
1104
1105 virtual void handler() = 0;
1106
1107 ComObjPtr<SessionMachine> pMachine;
1108 ComObjPtr<Progress> pProgress;
1109 const MachineState_T machineStateBackup;
1110 ComObjPtr<Snapshot> pSnapshot;
1111};
1112
1113/** Restore snapshot state task */
1114struct SessionMachine::RestoreSnapshotTask
1115 : public SessionMachine::SnapshotTask
1116{
1117 RestoreSnapshotTask(SessionMachine *m,
1118 Progress *p,
1119 Snapshot *s,
1120 ULONG ulStateFileSizeMB)
1121 : SnapshotTask(m, p, s),
1122 m_ulStateFileSizeMB(ulStateFileSizeMB)
1123 {}
1124
1125 void handler()
1126 {
1127 pMachine->restoreSnapshotHandler(*this);
1128 }
1129
1130 ULONG m_ulStateFileSizeMB;
1131};
1132
1133/** Discard snapshot task */
1134struct SessionMachine::DeleteSnapshotTask
1135 : public SessionMachine::SnapshotTask
1136{
1137 DeleteSnapshotTask(SessionMachine *m,
1138 Progress *p,
1139 Snapshot *s)
1140 : SnapshotTask(m, p, s)
1141 {}
1142
1143 void handler()
1144 {
1145 pMachine->deleteSnapshotHandler(*this);
1146 }
1147
1148private:
1149 DeleteSnapshotTask(const SnapshotTask &task)
1150 : SnapshotTask(task)
1151 {}
1152};
1153
1154/**
1155 * Static SessionMachine method that can get passed to RTThreadCreate to
1156 * have a thread started for a SnapshotTask. See SnapshotTask above.
1157 *
1158 * This calls either RestoreSnapshotTask::handler() or DeleteSnapshotTask::handler().
1159 */
1160
1161/* static */ DECLCALLBACK(int) SessionMachine::taskHandler(RTTHREAD /* thread */, void *pvUser)
1162{
1163 AssertReturn(pvUser, VERR_INVALID_POINTER);
1164
1165 SnapshotTask *task = static_cast<SnapshotTask*>(pvUser);
1166 task->handler();
1167
1168 // it's our responsibility to delete the task
1169 delete task;
1170
1171 return 0;
1172}
1173
1174////////////////////////////////////////////////////////////////////////////////
1175//
1176// TakeSnapshot methods (SessionMachine and related tasks)
1177//
1178////////////////////////////////////////////////////////////////////////////////
1179
1180/**
1181 * Implementation for IInternalMachineControl::beginTakingSnapshot().
1182 *
1183 * Gets called indirectly from Console::TakeSnapshot, which creates a
1184 * progress object in the client and then starts a thread
1185 * (Console::fntTakeSnapshotWorker) which then calls this.
1186 *
1187 * In other words, the asynchronous work for taking snapshots takes place
1188 * on the _client_ (in the Console). This is different from restoring
1189 * or deleting snapshots, which start threads on the server.
1190 *
1191 * This does the server-side work of taking a snapshot: it creates diffencing
1192 * images for all hard disks attached to the machine and then creates a
1193 * Snapshot object with a corresponding SnapshotMachine to save the VM settings.
1194 *
1195 * The client's fntTakeSnapshotWorker() blocks while this takes place.
1196 * After this returns successfully, fntTakeSnapshotWorker() will begin
1197 * saving the machine state to the snapshot object and reconfigure the
1198 * hard disks.
1199 *
1200 * When the console is done, it calls SessionMachine::EndTakingSnapshot().
1201 *
1202 * @note Locks mParent + this object for writing.
1203 *
1204 * @param aInitiator in: The console on which Console::TakeSnapshot was called.
1205 * @param aName in: The name for the new snapshot.
1206 * @param aDescription in: A description for the new snapshot.
1207 * @param aConsoleProgress in: The console's (client's) progress object.
1208 * @param fTakingSnapshotOnline in: True if an online snapshot is being taken (i.e. machine is running).
1209 * @param aStateFilePath out: name of file in snapshots folder to which the console should write the VM state.
1210 * @return
1211 */
1212STDMETHODIMP SessionMachine::BeginTakingSnapshot(IConsole *aInitiator,
1213 IN_BSTR aName,
1214 IN_BSTR aDescription,
1215 IProgress *aConsoleProgress,
1216 BOOL fTakingSnapshotOnline,
1217 BSTR *aStateFilePath)
1218{
1219 LogFlowThisFuncEnter();
1220
1221 AssertReturn(aInitiator && aName, E_INVALIDARG);
1222 AssertReturn(aStateFilePath, E_POINTER);
1223
1224 LogFlowThisFunc(("aName='%ls' fTakingSnapshotOnline=%RTbool\n", aName, fTakingSnapshotOnline));
1225
1226 AutoCaller autoCaller(this);
1227 AssertComRCReturn(autoCaller.rc(), autoCaller.rc());
1228
1229 /* saveSettings() needs mParent lock */
1230 AutoMultiWriteLock2 alock(mParent, this COMMA_LOCKVAL_SRC_POS);
1231
1232 AssertReturn( !Global::IsOnlineOrTransient(mData->mMachineState)
1233 || mData->mMachineState == MachineState_Running
1234 || mData->mMachineState == MachineState_Paused, E_FAIL);
1235 AssertReturn(mSnapshotData.mLastState == MachineState_Null, E_FAIL);
1236 AssertReturn(mSnapshotData.mSnapshot.isNull(), E_FAIL);
1237
1238 if ( !fTakingSnapshotOnline
1239 && mData->mMachineState != MachineState_Saved
1240 )
1241 {
1242 /* save all current settings to ensure current changes are committed and
1243 * hard disks are fixed up */
1244 HRESULT rc = saveSettings();
1245 if (FAILED(rc)) return rc;
1246 }
1247
1248 /* create an ID for the snapshot */
1249 Guid snapshotId;
1250 snapshotId.create();
1251
1252 Utf8Str strStateFilePath;
1253 /* stateFilePath is null when the machine is not online nor saved */
1254 if ( fTakingSnapshotOnline
1255 || mData->mMachineState == MachineState_Saved)
1256 {
1257 strStateFilePath = Utf8StrFmt("%ls%c{%RTuuid}.sav",
1258 mUserData->mSnapshotFolderFull.raw(),
1259 RTPATH_DELIMITER,
1260 snapshotId.ptr());
1261 /* ensure the directory for the saved state file exists */
1262 HRESULT rc = VirtualBox::ensureFilePathExists(strStateFilePath);
1263 if (FAILED(rc)) return rc;
1264 }
1265
1266 /* create a snapshot machine object */
1267 ComObjPtr<SnapshotMachine> snapshotMachine;
1268 snapshotMachine.createObject();
1269 HRESULT rc = snapshotMachine->init(this, snapshotId, strStateFilePath);
1270 AssertComRCReturn(rc, rc);
1271
1272 /* create a snapshot object */
1273 RTTIMESPEC time;
1274 ComObjPtr<Snapshot> pSnapshot;
1275 pSnapshot.createObject();
1276 rc = pSnapshot->init(mParent,
1277 snapshotId,
1278 aName,
1279 aDescription,
1280 *RTTimeNow(&time),
1281 snapshotMachine,
1282 mData->mCurrentSnapshot);
1283 AssertComRCReturnRC(rc);
1284
1285 /* fill in the snapshot data */
1286 mSnapshotData.mLastState = mData->mMachineState;
1287 mSnapshotData.mSnapshot = pSnapshot;
1288
1289 try
1290 {
1291 LogFlowThisFunc(("Creating differencing hard disks (online=%d)...\n",
1292 fTakingSnapshotOnline));
1293
1294 // backup the media data so we can recover if things goes wrong along the day;
1295 // the matching commit() is in fixupMedia() during endSnapshot()
1296 mMediaData.backup();
1297
1298 /* Console::fntTakeSnapshotWorker and friends expects this. */
1299 if (mSnapshotData.mLastState == MachineState_Running)
1300 setMachineState(MachineState_LiveSnapshotting);
1301 else
1302 setMachineState(MachineState_Saving); /** @todo Confusing! Saving is used for both online and offline snapshots. */
1303
1304 /* create new differencing hard disks and attach them to this machine */
1305 rc = createImplicitDiffs(mUserData->mSnapshotFolderFull,
1306 aConsoleProgress,
1307 1, // operation weight; must be the same as in Console::TakeSnapshot()
1308 !!fTakingSnapshotOnline);
1309 if (FAILED(rc))
1310 throw rc;
1311
1312 if (mSnapshotData.mLastState == MachineState_Saved)
1313 {
1314 Utf8Str stateFrom = mSSData->mStateFilePath;
1315 Utf8Str stateTo = mSnapshotData.mSnapshot->stateFilePath();
1316
1317 LogFlowThisFunc(("Copying the execution state from '%s' to '%s'...\n",
1318 stateFrom.raw(), stateTo.raw()));
1319
1320 aConsoleProgress->SetNextOperation(Bstr(tr("Copying the execution state")),
1321 1); // weight
1322
1323 /* Leave the lock before a lengthy operation (mMachineState is
1324 * MachineState_Saving here) */
1325 alock.leave();
1326
1327 /* copy the state file */
1328 int vrc = RTFileCopyEx(stateFrom.c_str(),
1329 stateTo.c_str(),
1330 0,
1331 progressCallback,
1332 aConsoleProgress);
1333 alock.enter();
1334
1335 if (RT_FAILURE(vrc))
1336 {
1337 /** @todo r=bird: Delete stateTo when appropriate. */
1338 throw setError(E_FAIL,
1339 tr("Could not copy the state file '%s' to '%s' (%Rrc)"),
1340 stateFrom.raw(),
1341 stateTo.raw(),
1342 vrc);
1343 }
1344 }
1345 }
1346 catch (HRESULT hrc)
1347 {
1348 LogThisFunc(("Caught %Rhrc [%s]\n", hrc, Global::stringifyMachineState(mData->mMachineState) ));
1349 if ( mSnapshotData.mLastState != mData->mMachineState
1350 && ( mSnapshotData.mLastState == MachineState_Running
1351 ? mData->mMachineState == MachineState_LiveSnapshotting
1352 : mData->mMachineState == MachineState_Saving)
1353 )
1354 setMachineState(mSnapshotData.mLastState);
1355
1356 pSnapshot->uninit();
1357 pSnapshot.setNull();
1358 mSnapshotData.mLastState = MachineState_Null;
1359 mSnapshotData.mSnapshot.setNull();
1360
1361 rc = hrc;
1362 }
1363
1364 if (fTakingSnapshotOnline && SUCCEEDED(rc))
1365 strStateFilePath.cloneTo(aStateFilePath);
1366 else
1367 *aStateFilePath = NULL;
1368
1369 LogFlowThisFunc(("LEAVE - %Rhrc [%s]\n", rc, Global::stringifyMachineState(mData->mMachineState) ));
1370 return rc;
1371}
1372
1373/**
1374 * Implementation for IInternalMachineControl::beginTakingSnapshot().
1375 *
1376 * Called by the Console when it's done saving the VM state into the snapshot
1377 * (if online) and reconfiguring the hard disks. See BeginTakingSnapshot() above.
1378 *
1379 * This also gets called if the console part of snapshotting failed after the
1380 * BeginTakingSnapshot() call, to clean up the server side.
1381 *
1382 * @note Locks this object for writing.
1383 *
1384 * @param aSuccess Whether Console was successful with the client-side snapshot things.
1385 * @return
1386 */
1387STDMETHODIMP SessionMachine::EndTakingSnapshot(BOOL aSuccess)
1388{
1389 LogFlowThisFunc(("\n"));
1390
1391 AutoCaller autoCaller(this);
1392 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
1393
1394 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1395
1396 AssertReturn( !aSuccess
1397 || ( ( mData->mMachineState == MachineState_Saving
1398 || mData->mMachineState == MachineState_LiveSnapshotting)
1399 && mSnapshotData.mLastState != MachineState_Null
1400 && !mSnapshotData.mSnapshot.isNull()
1401 )
1402 , E_FAIL);
1403
1404 /*
1405 * Restore the state we had when BeginTakingSnapshot() was called,
1406 * Console::fntTakeSnapshotWorker restores its local copy when we return.
1407 * If the state was Running, then let Console::fntTakeSnapshotWorker do it
1408 * all to avoid races.
1409 */
1410 if ( mData->mMachineState != mSnapshotData.mLastState
1411 && mSnapshotData.mLastState != MachineState_Running)
1412 setMachineState(mSnapshotData.mLastState);
1413
1414 return endTakingSnapshot(aSuccess);
1415}
1416
1417/**
1418 * Internal helper method to finalize taking a snapshot. Gets called from
1419 * SessionMachine::EndTakingSnapshot() to finalize the server-side
1420 * parts of snapshotting.
1421 *
1422 * This also gets called from SessionMachine::uninit() if an untaken
1423 * snapshot needs cleaning up.
1424 *
1425 * Expected to be called after completing *all* the tasks related to
1426 * taking the snapshot, either successfully or unsuccessfilly.
1427 *
1428 * @param aSuccess TRUE if the snapshot has been taken successfully.
1429 *
1430 * @note Locks this objects for writing.
1431 */
1432HRESULT SessionMachine::endTakingSnapshot(BOOL aSuccess)
1433{
1434 LogFlowThisFuncEnter();
1435
1436 AutoCaller autoCaller(this);
1437 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
1438
1439 AutoMultiWriteLock2 alock(mParent, this COMMA_LOCKVAL_SRC_POS);
1440 // saveSettings needs VirtualBox lock
1441
1442 AssertReturn(!mSnapshotData.mSnapshot.isNull(), E_FAIL);
1443
1444 MultiResult rc(S_OK);
1445
1446 ComObjPtr<Snapshot> pOldFirstSnap = mData->mFirstSnapshot;
1447 ComObjPtr<Snapshot> pOldCurrentSnap = mData->mCurrentSnapshot;
1448
1449 bool fOnline = Global::IsOnline(mSnapshotData.mLastState);
1450
1451 if (aSuccess)
1452 {
1453 // new snapshot becomes the current one
1454 mData->mCurrentSnapshot = mSnapshotData.mSnapshot;
1455
1456 /* memorize the first snapshot if necessary */
1457 if (!mData->mFirstSnapshot)
1458 mData->mFirstSnapshot = mData->mCurrentSnapshot;
1459
1460 if (!fOnline)
1461 /* the machine was powered off or saved when taking a snapshot, so
1462 * reset the mCurrentStateModified flag */
1463 mData->mCurrentStateModified = FALSE;
1464
1465 rc = saveSettings();
1466 }
1467
1468 if (aSuccess && SUCCEEDED(rc))
1469 {
1470 /* associate old hard disks with the snapshot and do locking/unlocking*/
1471 fixupMedia(true /* aCommit */, fOnline);
1472
1473 /* inform callbacks */
1474 mParent->onSnapshotTaken(mData->mUuid,
1475 mSnapshotData.mSnapshot->getId());
1476 }
1477 else
1478 {
1479 /* delete all differencing hard disks created (this will also attach
1480 * their parents back by rolling back mMediaData) */
1481 fixupMedia(false /* aCommit */);
1482
1483 mData->mFirstSnapshot = pOldFirstSnap; // might have been changed above
1484 mData->mCurrentSnapshot = pOldCurrentSnap; // might have been changed above
1485
1486 /* delete the saved state file (it might have been already created) */
1487 if (mSnapshotData.mSnapshot->stateFilePath().length())
1488 RTFileDelete(mSnapshotData.mSnapshot->stateFilePath().c_str());
1489
1490 mSnapshotData.mSnapshot->uninit();
1491 }
1492
1493 /* clear out the snapshot data */
1494 mSnapshotData.mLastState = MachineState_Null;
1495 mSnapshotData.mSnapshot.setNull();
1496
1497 LogFlowThisFuncLeave();
1498 return rc;
1499}
1500
1501////////////////////////////////////////////////////////////////////////////////
1502//
1503// RestoreSnapshot methods (SessionMachine and related tasks)
1504//
1505////////////////////////////////////////////////////////////////////////////////
1506
1507/**
1508 * Implementation for IInternalMachineControl::restoreSnapshot().
1509 *
1510 * Gets called from Console::RestoreSnapshot(), and that's basically the
1511 * only thing Console does. Restoring a snapshot happens entirely on the
1512 * server side since the machine cannot be running.
1513 *
1514 * This creates a new thread that does the work and returns a progress
1515 * object to the client which is then returned to the caller of
1516 * Console::RestoreSnapshot().
1517 *
1518 * Actual work then takes place in RestoreSnapshotTask::handler().
1519 *
1520 * @note Locks this + children objects for writing!
1521 *
1522 * @param aInitiator in: rhe console on which Console::RestoreSnapshot was called.
1523 * @param aSnapshot in: the snapshot to restore.
1524 * @param aMachineState in: client-side machine state.
1525 * @param aProgress out: progress object to monitor restore thread.
1526 * @return
1527 */
1528STDMETHODIMP SessionMachine::RestoreSnapshot(IConsole *aInitiator,
1529 ISnapshot *aSnapshot,
1530 MachineState_T *aMachineState,
1531 IProgress **aProgress)
1532{
1533 LogFlowThisFuncEnter();
1534
1535 AssertReturn(aInitiator, E_INVALIDARG);
1536 AssertReturn(aSnapshot && aMachineState && aProgress, E_POINTER);
1537
1538 AutoCaller autoCaller(this);
1539 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
1540
1541 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1542
1543 // machine must not be running
1544 ComAssertRet(!Global::IsOnlineOrTransient(mData->mMachineState),
1545 E_FAIL);
1546
1547 ComObjPtr<Snapshot> pSnapshot(static_cast<Snapshot*>(aSnapshot));
1548 ComObjPtr<SnapshotMachine> pSnapMachine = pSnapshot->getSnapshotMachine();
1549
1550 // create a progress object. The number of operations is:
1551 // 1 (preparing) + # of hard disks + 1 (if we need to copy the saved state file) */
1552 LogFlowThisFunc(("Going thru snapshot machine attachments to determine progress setup\n"));
1553
1554 ULONG ulOpCount = 1; // one for preparations
1555 ULONG ulTotalWeight = 1; // one for preparations
1556 for (MediaData::AttachmentList::iterator it = pSnapMachine->mMediaData->mAttachments.begin();
1557 it != pSnapMachine->mMediaData->mAttachments.end();
1558 ++it)
1559 {
1560 ComObjPtr<MediumAttachment> &pAttach = *it;
1561 AutoReadLock attachLock(pAttach COMMA_LOCKVAL_SRC_POS);
1562 if (pAttach->getType() == DeviceType_HardDisk)
1563 {
1564 ++ulOpCount;
1565 ++ulTotalWeight; // assume one MB weight for each differencing hard disk to manage
1566 Assert(pAttach->getMedium());
1567 LogFlowThisFunc(("op %d: considering hard disk attachment %s\n", ulOpCount, pAttach->getMedium()->getName().c_str()));
1568 }
1569 }
1570
1571 ULONG ulStateFileSizeMB = 0;
1572 if (pSnapshot->stateFilePath().length())
1573 {
1574 ++ulOpCount; // one for the saved state
1575
1576 uint64_t ullSize;
1577 int irc = RTFileQuerySize(pSnapshot->stateFilePath().c_str(), &ullSize);
1578 if (!RT_SUCCESS(irc))
1579 // if we can't access the file here, then we'll be doomed later also, so fail right away
1580 setError(E_FAIL, tr("Cannot access state file '%s', runtime error, %Rra"), pSnapshot->stateFilePath().c_str(), irc);
1581 if (ullSize == 0) // avoid division by zero
1582 ullSize = _1M;
1583
1584 ulStateFileSizeMB = (ULONG)(ullSize / _1M);
1585 LogFlowThisFunc(("op %d: saved state file '%s' has %RI64 bytes (%d MB)\n",
1586 ulOpCount, pSnapshot->stateFilePath().raw(), ullSize, ulStateFileSizeMB));
1587
1588 ulTotalWeight += ulStateFileSizeMB;
1589 }
1590
1591 ComObjPtr<Progress> pProgress;
1592 pProgress.createObject();
1593 pProgress->init(mParent, aInitiator,
1594 BstrFmt(tr("Restoring snapshot '%s'"), pSnapshot->getName().c_str()),
1595 FALSE /* aCancelable */,
1596 ulOpCount,
1597 ulTotalWeight,
1598 Bstr(tr("Restoring machine settings")),
1599 1);
1600
1601 /* create and start the task on a separate thread (note that it will not
1602 * start working until we release alock) */
1603 RestoreSnapshotTask *task = new RestoreSnapshotTask(this,
1604 pProgress,
1605 pSnapshot,
1606 ulStateFileSizeMB);
1607 int vrc = RTThreadCreate(NULL,
1608 taskHandler,
1609 (void*)task,
1610 0,
1611 RTTHREADTYPE_MAIN_WORKER,
1612 0,
1613 "RestoreSnap");
1614 if (RT_FAILURE(vrc))
1615 {
1616 delete task;
1617 ComAssertRCRet(vrc, E_FAIL);
1618 }
1619
1620 /* set the proper machine state (note: after creating a Task instance) */
1621 setMachineState(MachineState_RestoringSnapshot);
1622
1623 /* return the progress to the caller */
1624 pProgress.queryInterfaceTo(aProgress);
1625
1626 /* return the new state to the caller */
1627 *aMachineState = mData->mMachineState;
1628
1629 LogFlowThisFuncLeave();
1630
1631 return S_OK;
1632}
1633
1634/**
1635 * Worker method for the restore snapshot thread created by SessionMachine::RestoreSnapshot().
1636 * This method gets called indirectly through SessionMachine::taskHandler() which then
1637 * calls RestoreSnapshotTask::handler().
1638 *
1639 * The RestoreSnapshotTask contains the progress object returned to the console by
1640 * SessionMachine::RestoreSnapshot, through which progress and results are reported.
1641 *
1642 * @note Locks mParent + this object for writing.
1643 *
1644 * @param aTask Task data.
1645 */
1646void SessionMachine::restoreSnapshotHandler(RestoreSnapshotTask &aTask)
1647{
1648 LogFlowThisFuncEnter();
1649
1650 AutoCaller autoCaller(this);
1651
1652 LogFlowThisFunc(("state=%d\n", autoCaller.state()));
1653 if (!autoCaller.isOk())
1654 {
1655 /* we might have been uninitialized because the session was accidentally
1656 * closed by the client, so don't assert */
1657 aTask.pProgress->notifyComplete(E_FAIL,
1658 COM_IIDOF(IMachine),
1659 getComponentName(),
1660 tr("The session has been accidentally closed"));
1661
1662 LogFlowThisFuncLeave();
1663 return;
1664 }
1665
1666 /* saveSettings() needs mParent lock */
1667 AutoWriteLock vboxLock(mParent COMMA_LOCKVAL_SRC_POS);
1668
1669 /* @todo We don't need mParent lock so far so unlock() it. Better is to
1670 * provide an AutoWriteLock argument that lets create a non-locking
1671 * instance */
1672 vboxLock.release();
1673
1674 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1675
1676 /* discard all current changes to mUserData (name, OSType etc.) (note that
1677 * the machine is powered off, so there is no need to inform the direct
1678 * session) */
1679 if (isModified())
1680 rollback(false /* aNotify */);
1681
1682 HRESULT rc = S_OK;
1683
1684 bool stateRestored = false;
1685
1686 try
1687 {
1688 /* discard the saved state file if the machine was Saved prior to this
1689 * operation */
1690 if (aTask.machineStateBackup == MachineState_Saved)
1691 {
1692 Assert(!mSSData->mStateFilePath.isEmpty());
1693 RTFileDelete(mSSData->mStateFilePath.c_str());
1694 mSSData->mStateFilePath.setNull();
1695 aTask.modifyBackedUpState(MachineState_PoweredOff);
1696 rc = saveStateSettings(SaveSTS_StateFilePath);
1697 if (FAILED(rc)) throw rc;
1698 }
1699
1700 RTTIMESPEC snapshotTimeStamp;
1701 RTTimeSpecSetMilli(&snapshotTimeStamp, 0);
1702
1703 {
1704 AutoReadLock snapshotLock(aTask.pSnapshot COMMA_LOCKVAL_SRC_POS);
1705
1706 /* remember the timestamp of the snapshot we're restoring from */
1707 snapshotTimeStamp = aTask.pSnapshot->getTimeStamp();
1708
1709 ComPtr<SnapshotMachine> pSnapshotMachine(aTask.pSnapshot->getSnapshotMachine());
1710
1711 /* copy all hardware data from the snapshot */
1712 copyFrom(pSnapshotMachine);
1713
1714 LogFlowThisFunc(("Restoring hard disks from the snapshot...\n"));
1715
1716 /* restore the attachments from the snapshot */
1717 mMediaData.backup();
1718 mMediaData->mAttachments = pSnapshotMachine->mMediaData->mAttachments;
1719
1720 /* leave the locks before the potentially lengthy operation */
1721 snapshotLock.release();
1722 alock.leave();
1723
1724 rc = createImplicitDiffs(mUserData->mSnapshotFolderFull,
1725 aTask.pProgress,
1726 1,
1727 false /* aOnline */);
1728 if (FAILED(rc)) throw rc;
1729
1730 alock.enter();
1731 snapshotLock.acquire();
1732
1733 /* Note: on success, current (old) hard disks will be
1734 * deassociated/deleted on #commit() called from #saveSettings() at
1735 * the end. On failure, newly created implicit diffs will be
1736 * deleted by #rollback() at the end. */
1737
1738 /* should not have a saved state file associated at this point */
1739 Assert(mSSData->mStateFilePath.isEmpty());
1740
1741 if (!aTask.pSnapshot->stateFilePath().isEmpty())
1742 {
1743 Utf8Str snapStateFilePath = aTask.pSnapshot->stateFilePath();
1744
1745 Utf8Str stateFilePath = Utf8StrFmt("%ls%c{%RTuuid}.sav",
1746 mUserData->mSnapshotFolderFull.raw(),
1747 RTPATH_DELIMITER,
1748 mData->mUuid.raw());
1749
1750 LogFlowThisFunc(("Copying saved state file from '%s' to '%s'...\n",
1751 snapStateFilePath.raw(), stateFilePath.raw()));
1752
1753 aTask.pProgress->SetNextOperation(Bstr(tr("Restoring the execution state")),
1754 aTask.m_ulStateFileSizeMB); // weight
1755
1756 /* leave the lock before the potentially lengthy operation */
1757 snapshotLock.release();
1758 alock.leave();
1759
1760 /* copy the state file */
1761 int vrc = RTFileCopyEx(snapStateFilePath.c_str(),
1762 stateFilePath.c_str(),
1763 0,
1764 progressCallback,
1765 static_cast<IProgress*>(aTask.pProgress));
1766
1767 alock.enter();
1768 snapshotLock.acquire();
1769
1770 if (RT_SUCCESS(vrc))
1771 mSSData->mStateFilePath = stateFilePath;
1772 else
1773 throw setError(E_FAIL,
1774 tr("Could not copy the state file '%s' to '%s' (%Rrc)"),
1775 snapStateFilePath.raw(),
1776 stateFilePath.raw(),
1777 vrc);
1778 }
1779
1780 LogFlowThisFunc(("Setting new current snapshot {%RTuuid}\n", aTask.pSnapshot->getId().raw()));
1781 /* make the snapshot we restored from the current snapshot */
1782 mData->mCurrentSnapshot = aTask.pSnapshot;
1783 }
1784
1785 /* grab differencing hard disks from the old attachments that will
1786 * become unused and need to be auto-deleted */
1787
1788 std::list< ComObjPtr<MediumAttachment> > llDiffAttachmentsToDelete;
1789
1790 for (MediaData::AttachmentList::const_iterator it = mMediaData.backedUpData()->mAttachments.begin();
1791 it != mMediaData.backedUpData()->mAttachments.end();
1792 ++it)
1793 {
1794 ComObjPtr<MediumAttachment> pAttach = *it;
1795 ComObjPtr<Medium> pMedium = pAttach->getMedium();
1796
1797 /* while the hard disk is attached, the number of children or the
1798 * parent cannot change, so no lock */
1799 if ( !pMedium.isNull()
1800 && pAttach->getType() == DeviceType_HardDisk
1801 && !pMedium->getParent().isNull()
1802 && pMedium->getChildren().size() == 0
1803 )
1804 {
1805 LogFlowThisFunc(("Picked differencing image '%s' for deletion\n", pMedium->getName().raw()));
1806
1807 llDiffAttachmentsToDelete.push_back(pAttach);
1808 }
1809 }
1810
1811 int saveFlags = 0;
1812
1813 /* @todo saveSettings() below needs a VirtualBox write lock and we need
1814 * to leave this object's lock to do this to follow the {parent-child}
1815 * locking rule. This is the last chance to do that while we are still
1816 * in a protective state which allows us to temporarily leave the lock*/
1817 alock.release();
1818 vboxLock.acquire();
1819 alock.acquire();
1820
1821 /* we have already discarded the current state, so set the execution
1822 * state accordingly no matter of the discard snapshot result */
1823 if (!mSSData->mStateFilePath.isEmpty())
1824 setMachineState(MachineState_Saved);
1825 else
1826 setMachineState(MachineState_PoweredOff);
1827
1828 updateMachineStateOnClient();
1829 stateRestored = true;
1830
1831 /* assign the timestamp from the snapshot */
1832 Assert(RTTimeSpecGetMilli (&snapshotTimeStamp) != 0);
1833 mData->mLastStateChange = snapshotTimeStamp;
1834
1835 // detach the current-state diffs that we detected above and build a list of
1836 // images to delete _after_ saveSettings()
1837
1838 MediaList llDiffsToDelete;
1839
1840 for (std::list< ComObjPtr<MediumAttachment> >::iterator it = llDiffAttachmentsToDelete.begin();
1841 it != llDiffAttachmentsToDelete.end();
1842 ++it)
1843 {
1844 ComObjPtr<MediumAttachment> pAttach = *it; // guaranteed to have only attachments where medium != NULL
1845 ComObjPtr<Medium> pMedium = pAttach->getMedium();
1846
1847 AutoWriteLock mlock(pMedium COMMA_LOCKVAL_SRC_POS);
1848
1849 LogFlowThisFunc(("Detaching old current state in differencing image '%s'\n", pMedium->getName().raw()));
1850
1851 // Normally we "detach" the medium by removing the attachment object
1852 // from the current machine data; saveSettings() below would then
1853 // compare the current machine data with the one in the backup
1854 // and actually call Medium::detachFrom(). But that works only half
1855 // the time in our case so instead we force a detachment here:
1856 // remove from machine data
1857 mMediaData->mAttachments.remove(pAttach);
1858 // remove it from the backup or else saveSettings will try to detach
1859 // it again and assert
1860 mMediaData.backedUpData()->mAttachments.remove(pAttach);
1861 // then clean up backrefs
1862 pMedium->detachFrom(mData->mUuid);
1863
1864 llDiffsToDelete.push_back(pMedium);
1865 }
1866
1867 // save all settings, reset the modified flag and commit;
1868 rc = saveSettings(SaveS_ResetCurStateModified | saveFlags);
1869 if (FAILED(rc)) throw rc;
1870
1871 // from here on we cannot roll back on failure any more
1872
1873 for (MediaList::iterator it = llDiffsToDelete.begin();
1874 it != llDiffsToDelete.end();
1875 ++it)
1876 {
1877 ComObjPtr<Medium> &pMedium = *it;
1878 LogFlowThisFunc(("Deleting old current state in differencing image '%s'\n", pMedium->getName().raw()));
1879
1880 HRESULT rc2 = pMedium->deleteStorageAndWait();
1881 // ignore errors here because we cannot roll back after saveSettings() above
1882 if (SUCCEEDED(rc2))
1883 pMedium->uninit();
1884 }
1885 }
1886 catch (HRESULT aRC)
1887 {
1888 rc = aRC;
1889 }
1890
1891 if (FAILED(rc))
1892 {
1893 /* preserve existing error info */
1894 ErrorInfoKeeper eik;
1895
1896 /* undo all changes on failure */
1897 rollback(false /* aNotify */);
1898
1899 if (!stateRestored)
1900 {
1901 /* restore the machine state */
1902 setMachineState(aTask.machineStateBackup);
1903 updateMachineStateOnClient();
1904 }
1905 }
1906
1907 /* set the result (this will try to fetch current error info on failure) */
1908 aTask.pProgress->notifyComplete(rc);
1909
1910 if (SUCCEEDED(rc))
1911 mParent->onSnapshotDeleted(mData->mUuid, Guid());
1912
1913 LogFlowThisFunc(("Done restoring snapshot (rc=%08X)\n", rc));
1914
1915 LogFlowThisFuncLeave();
1916}
1917
1918////////////////////////////////////////////////////////////////////////////////
1919//
1920// DeleteSnapshot methods (SessionMachine and related tasks)
1921//
1922////////////////////////////////////////////////////////////////////////////////
1923
1924/**
1925 * Implementation for IInternalMachineControl::deleteSnapshot().
1926 *
1927 * Gets called from Console::DeleteSnapshot(), and that's basically the
1928 * only thing Console does. Deleting a snapshot happens entirely on the
1929 * server side since the machine cannot be running.
1930 *
1931 * This creates a new thread that does the work and returns a progress
1932 * object to the client which is then returned to the caller of
1933 * Console::DeleteSnapshot().
1934 *
1935 * Actual work then takes place in DeleteSnapshotTask::handler().
1936 *
1937 * @note Locks mParent + this + children objects for writing!
1938 */
1939STDMETHODIMP SessionMachine::DeleteSnapshot(IConsole *aInitiator,
1940 IN_BSTR aId,
1941 MachineState_T *aMachineState,
1942 IProgress **aProgress)
1943{
1944 LogFlowThisFuncEnter();
1945
1946 Guid id(aId);
1947 AssertReturn(aInitiator && !id.isEmpty(), E_INVALIDARG);
1948 AssertReturn(aMachineState && aProgress, E_POINTER);
1949
1950 AutoCaller autoCaller(this);
1951 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
1952
1953 /* saveSettings() needs mParent lock */
1954 AutoMultiWriteLock2 alock(mParent, this COMMA_LOCKVAL_SRC_POS);
1955
1956 // machine must not be running
1957 ComAssertRet(!Global::IsOnlineOrTransient(mData->mMachineState), E_FAIL);
1958
1959 AutoWriteLock treeLock(snapshotsTreeLockHandle() COMMA_LOCKVAL_SRC_POS);
1960
1961 ComObjPtr<Snapshot> pSnapshot;
1962 HRESULT rc = findSnapshot(id, pSnapshot, true /* aSetError */);
1963 if (FAILED(rc)) return rc;
1964
1965 AutoWriteLock snapshotLock(pSnapshot COMMA_LOCKVAL_SRC_POS);
1966
1967 size_t childrenCount = pSnapshot->getChildrenCount();
1968 if (childrenCount > 1)
1969 return setError(VBOX_E_INVALID_OBJECT_STATE,
1970 tr("Snapshot '%s' of the machine '%ls' cannot be deleted. because it has %d child snapshots, which is more than the one snapshot allowed for deletion"),
1971 pSnapshot->getName().c_str(),
1972 mUserData->mName.raw(),
1973 childrenCount);
1974
1975 /* If the snapshot being discarded is the current one, ensure current
1976 * settings are committed and saved.
1977 */
1978 if (pSnapshot == mData->mCurrentSnapshot)
1979 {
1980 if (isModified())
1981 {
1982 rc = saveSettings();
1983 if (FAILED(rc)) return rc;
1984 }
1985 }
1986
1987 ComObjPtr<SnapshotMachine> pSnapMachine = pSnapshot->getSnapshotMachine();
1988
1989 /* create a progress object. The number of operations is:
1990 * 1 (preparing) + 1 if the snapshot is online + # of normal hard disks
1991 */
1992 LogFlowThisFunc(("Going thru snapshot machine attachments to determine progress setup\n"));
1993
1994 ULONG ulOpCount = 1; // one for preparations
1995 ULONG ulTotalWeight = 1; // one for preparations
1996
1997 if (pSnapshot->stateFilePath().length())
1998 {
1999 ++ulOpCount;
2000 ++ulTotalWeight; // assume 1 MB for deleting the state file
2001 }
2002
2003 // count normal hard disks and add their sizes to the weight
2004 for (MediaData::AttachmentList::iterator it = pSnapMachine->mMediaData->mAttachments.begin();
2005 it != pSnapMachine->mMediaData->mAttachments.end();
2006 ++it)
2007 {
2008 ComObjPtr<MediumAttachment> &pAttach = *it;
2009 AutoReadLock attachLock(pAttach COMMA_LOCKVAL_SRC_POS);
2010 if (pAttach->getType() == DeviceType_HardDisk)
2011 {
2012 ComObjPtr<Medium> pHD = pAttach->getMedium();
2013 Assert(pHD);
2014 AutoReadLock mlock(pHD COMMA_LOCKVAL_SRC_POS);
2015 if (pHD->getType() == MediumType_Normal)
2016 {
2017 ++ulOpCount;
2018 ulTotalWeight += (ULONG)(pHD->getSize() / _1M);
2019 }
2020 LogFlowThisFunc(("op %d: considering hard disk attachment %s\n", ulOpCount, pHD->getName().c_str()));
2021 }
2022 }
2023
2024 ComObjPtr<Progress> pProgress;
2025 pProgress.createObject();
2026 pProgress->init(mParent, aInitiator,
2027 BstrFmt(tr("Deleting snapshot '%s'"), pSnapshot->getName().c_str()),
2028 FALSE /* aCancelable */,
2029 ulOpCount,
2030 ulTotalWeight,
2031 Bstr(tr("Setting up")),
2032 1);
2033
2034 /* create and start the task on a separate thread */
2035 DeleteSnapshotTask *task = new DeleteSnapshotTask(this, pProgress, pSnapshot);
2036 int vrc = RTThreadCreate(NULL,
2037 taskHandler,
2038 (void*)task,
2039 0,
2040 RTTHREADTYPE_MAIN_WORKER,
2041 0,
2042 "DeleteSnapshot");
2043 if (RT_FAILURE(vrc))
2044 {
2045 delete task;
2046 return E_FAIL;
2047 }
2048
2049 /* set the proper machine state (note: after creating a Task instance) */
2050 setMachineState(MachineState_DeletingSnapshot);
2051
2052 /* return the progress to the caller */
2053 pProgress.queryInterfaceTo(aProgress);
2054
2055 /* return the new state to the caller */
2056 *aMachineState = mData->mMachineState;
2057
2058 LogFlowThisFuncLeave();
2059
2060 return S_OK;
2061}
2062
2063/**
2064 * Helper struct for SessionMachine::deleteSnapshotHandler().
2065 */
2066struct MediumDiscardRec
2067{
2068 MediumDiscardRec()
2069 : chain(NULL)
2070 {}
2071
2072 MediumDiscardRec(const ComObjPtr<Medium> &aHd,
2073 Medium::MergeChain *aChain = NULL)
2074 : hd(aHd),
2075 chain(aChain)
2076 {}
2077
2078 MediumDiscardRec(const ComObjPtr<Medium> &aHd,
2079 Medium::MergeChain *aChain,
2080 const ComObjPtr<Medium> &aReplaceHd,
2081 const ComObjPtr<MediumAttachment> &aReplaceHda,
2082 const Guid &aSnapshotId)
2083 : hd(aHd),
2084 chain(aChain),
2085 replaceHd(aReplaceHd),
2086 replaceHda(aReplaceHda),
2087 snapshotId(aSnapshotId)
2088 {}
2089
2090 ComObjPtr<Medium> hd;
2091 Medium::MergeChain *chain;
2092 /* these are for the replace hard disk case: */
2093 ComObjPtr<Medium> replaceHd;
2094 ComObjPtr<MediumAttachment> replaceHda;
2095 Guid snapshotId;
2096};
2097
2098typedef std::list <MediumDiscardRec> MediumDiscardRecList;
2099
2100/**
2101 * Worker method for the delete snapshot thread created by SessionMachine::DeleteSnapshot().
2102 * This method gets called indirectly through SessionMachine::taskHandler() which then
2103 * calls DeleteSnapshotTask::handler().
2104 *
2105 * The DeleteSnapshotTask contains the progress object returned to the console by
2106 * SessionMachine::DeleteSnapshot, through which progress and results are reported.
2107 *
2108 * @note Locks mParent + this + child objects for writing!
2109 *
2110 * @param aTask Task data.
2111 */
2112void SessionMachine::deleteSnapshotHandler(DeleteSnapshotTask &aTask)
2113{
2114 LogFlowThisFuncEnter();
2115
2116 AutoCaller autoCaller(this);
2117
2118 LogFlowThisFunc(("state=%d\n", autoCaller.state()));
2119 if (!autoCaller.isOk())
2120 {
2121 /* we might have been uninitialized because the session was accidentally
2122 * closed by the client, so don't assert */
2123 aTask.pProgress->notifyComplete(E_FAIL,
2124 COM_IIDOF(IMachine),
2125 getComponentName(),
2126 tr("The session has been accidentally closed"));
2127 LogFlowThisFuncLeave();
2128 return;
2129 }
2130
2131 /* Locking order: */
2132 AutoMultiWriteLock3 alock(this->lockHandle(),
2133 this->snapshotsTreeLockHandle(),
2134 aTask.pSnapshot->lockHandle()
2135 COMMA_LOCKVAL_SRC_POS);
2136
2137 ComObjPtr<SnapshotMachine> pSnapMachine = aTask.pSnapshot->getSnapshotMachine();
2138 /* no need to lock the snapshot machine since it is const by definiton */
2139
2140 HRESULT rc = S_OK;
2141
2142 /* save the snapshot ID (for callbacks) */
2143 Guid snapshotId1 = aTask.pSnapshot->getId();
2144
2145 MediumDiscardRecList toDiscard;
2146
2147 bool settingsChanged = false;
2148
2149 try
2150 {
2151 /* first pass: */
2152 LogFlowThisFunc(("1: Checking hard disk merge prerequisites...\n"));
2153
2154 // go thru the attachments of the snapshot machine
2155 // (the media in here point to the disk states _before_ the snapshot
2156 // was taken, i.e. the state we're restoring to; for each such
2157 // medium, we will need to merge it with its one and only child (the
2158 // diff image holding the changes written after the snapshot was taken)
2159 for (MediaData::AttachmentList::iterator it = pSnapMachine->mMediaData->mAttachments.begin();
2160 it != pSnapMachine->mMediaData->mAttachments.end();
2161 ++it)
2162 {
2163 ComObjPtr<MediumAttachment> &pAttach = *it;
2164 AutoReadLock attachLock(pAttach COMMA_LOCKVAL_SRC_POS);
2165 if (pAttach->getType() == DeviceType_HardDisk)
2166 {
2167 Assert(pAttach->getMedium());
2168 ComObjPtr<Medium> pHD = pAttach->getMedium();
2169 // do not lock, prepareDiscared() has a write lock which will hang otherwise
2170
2171#ifdef DEBUG
2172 pHD->dumpBackRefs();
2173#endif
2174
2175 Medium::MergeChain *chain = NULL;
2176
2177 // needs to be discarded (merged with the child if any), check prerequisites
2178 rc = pHD->prepareDiscard(chain);
2179 if (FAILED(rc)) throw rc;
2180
2181 // for simplicity, we merge pHd onto its child (forward merge), not the
2182 // other way round, because that saves us from updating the attachments
2183 // for the machine that follows the snapshot (next snapshot or real machine),
2184 // unless it's a base image:
2185
2186 if ( pHD->getParent().isNull()
2187 && chain != NULL
2188 )
2189 {
2190 // parent is null -> this disk is a base hard disk: we will
2191 // then do a backward merge, i.e. merge its only child onto
2192 // the base disk; prepareDiscard() does necessary checks.
2193 // So here we need then to update the attachment that refers
2194 // to the child and have it point to the parent instead
2195
2196 /* The below assert would be nice but I don't want to move
2197 * Medium::MergeChain to the header just for that
2198 * Assert (!chain->isForward()); */
2199
2200 // prepareDiscard() should have raised an error already
2201 // if there was more than one child
2202 Assert(pHD->getChildren().size() == 1);
2203
2204 ComObjPtr<Medium> pReplaceHD = pHD->getChildren().front();
2205
2206 const Guid *pReplaceMachineId = pReplaceHD->getFirstMachineBackrefId();
2207 NOREF(pReplaceMachineId);
2208 Assert(pReplaceMachineId);
2209 Assert(*pReplaceMachineId == mData->mUuid);
2210
2211 Guid snapshotId;
2212 const Guid *pSnapshotId = pReplaceHD->getFirstMachineBackrefSnapshotId();
2213 if (pSnapshotId)
2214 snapshotId = *pSnapshotId;
2215
2216 HRESULT rc2 = S_OK;
2217
2218 attachLock.release();
2219
2220 // First we must detach the child (otherwise mergeTo() called
2221 // by discard() will assert because it will be going to delete
2222 // the child), so adjust the backreferences:
2223 // 1) detach the first child hard disk
2224 rc2 = pReplaceHD->detachFrom(mData->mUuid, snapshotId);
2225 AssertComRC(rc2);
2226 // 2) attach to machine and snapshot
2227 rc2 = pHD->attachTo(mData->mUuid, snapshotId);
2228 AssertComRC(rc2);
2229
2230 /* replace the hard disk in the attachment object */
2231 if (snapshotId.isEmpty())
2232 {
2233 /* in current state */
2234 AssertBreak(pAttach = findAttachment(mMediaData->mAttachments, pReplaceHD));
2235 }
2236 else
2237 {
2238 /* in snapshot */
2239 ComObjPtr<Snapshot> snapshot;
2240 rc2 = findSnapshot(snapshotId, snapshot);
2241 AssertComRC(rc2);
2242
2243 /* don't lock the snapshot; cannot be modified outside */
2244 MediaData::AttachmentList &snapAtts = snapshot->getSnapshotMachine()->mMediaData->mAttachments;
2245 AssertBreak(pAttach = findAttachment(snapAtts, pReplaceHD));
2246 }
2247
2248 AutoWriteLock attLock(pAttach COMMA_LOCKVAL_SRC_POS);
2249 pAttach->updateMedium(pHD, false /* aImplicit */);
2250
2251 toDiscard.push_back(MediumDiscardRec(pHD,
2252 chain,
2253 pReplaceHD,
2254 pAttach,
2255 snapshotId));
2256 continue;
2257 }
2258
2259 toDiscard.push_back(MediumDiscardRec(pHD, chain));
2260 }
2261 }
2262
2263 /* Now we checked that we can successfully merge all normal hard disks
2264 * (unless a runtime error like end-of-disc happens). Prior to
2265 * performing the actual merge, we want to discard the snapshot itself
2266 * and remove it from the XML file to make sure that a possible merge
2267 * ruintime error will not make this snapshot inconsistent because of
2268 * the partially merged or corrupted hard disks */
2269
2270 /* second pass: */
2271 LogFlowThisFunc(("2: Discarding snapshot...\n"));
2272
2273 {
2274 ComObjPtr<Snapshot> parentSnapshot = aTask.pSnapshot->getParent();
2275 Utf8Str stateFilePath = aTask.pSnapshot->stateFilePath();
2276
2277 /* Note that discarding the snapshot will deassociate it from the
2278 * hard disks which will allow the merge+delete operation for them*/
2279 aTask.pSnapshot->beginDiscard();
2280 aTask.pSnapshot->uninit();
2281
2282 rc = saveAllSnapshots();
2283 if (FAILED(rc)) throw rc;
2284
2285 /// @todo (dmik)
2286 // if we implement some warning mechanism later, we'll have
2287 // to return a warning if the state file path cannot be deleted
2288 if (!stateFilePath.isEmpty())
2289 {
2290 aTask.pProgress->SetNextOperation(Bstr(tr("Discarding the execution state")),
2291 1); // weight
2292
2293 RTFileDelete(stateFilePath.c_str());
2294 }
2295
2296 /// @todo NEWMEDIA to provide a good level of fauilt tolerance, we
2297 /// should restore the shapshot in the snapshot tree if
2298 /// saveSnapshotSettings fails. Actually, we may call
2299 /// #saveSnapshotSettings() with a special flag that will tell it to
2300 /// skip the given snapshot as if it would have been discarded and
2301 /// only actually discard it if the save operation succeeds.
2302 }
2303
2304 /* here we come when we've irrevesibly discarded the snapshot which
2305 * means that the VM settigns (our relevant changes to mData) need to be
2306 * saved too */
2307 /// @todo NEWMEDIA maybe save everything in one operation in place of
2308 /// saveSnapshotSettings() above
2309 settingsChanged = true;
2310
2311 /* third pass: */
2312 LogFlowThisFunc(("3: Performing actual hard disk merging...\n"));
2313
2314 /* leave the locks before the potentially lengthy operation */
2315 alock.leave();
2316
2317 /// @todo NEWMEDIA turn the following errors into warnings because the
2318 /// snapshot itself has been already deleted (and interpret these
2319 /// warnings properly on the GUI side)
2320
2321 for (MediumDiscardRecList::iterator it = toDiscard.begin();
2322 it != toDiscard.end();)
2323 {
2324 rc = it->hd->discard(aTask.pProgress,
2325 (ULONG)(it->hd->getSize() / _1M), // weight
2326 it->chain);
2327 if (FAILED(rc)) throw rc;
2328
2329 /* prevent from calling cancelDiscard() */
2330 it = toDiscard.erase(it);
2331 }
2332
2333 LogFlowThisFunc(("Entering locks again...\n"));
2334 alock.enter();
2335 LogFlowThisFunc(("Entered locks OK\n"));
2336 }
2337 catch (HRESULT aRC) { rc = aRC; }
2338
2339 if (FAILED(rc))
2340 {
2341 HRESULT rc2 = S_OK;
2342
2343 /* un-prepare the remaining hard disks */
2344 for (MediumDiscardRecList::const_iterator it = toDiscard.begin();
2345 it != toDiscard.end(); ++it)
2346 {
2347 it->hd->cancelDiscard (it->chain);
2348
2349 if (!it->replaceHd.isNull())
2350 {
2351 /* undo hard disk replacement */
2352
2353 rc2 = it->replaceHd->attachTo(mData->mUuid, it->snapshotId);
2354 AssertComRC(rc2);
2355
2356 rc2 = it->hd->detachFrom (mData->mUuid, it->snapshotId);
2357 AssertComRC(rc2);
2358
2359 AutoWriteLock attLock(it->replaceHda COMMA_LOCKVAL_SRC_POS);
2360 it->replaceHda->updateMedium(it->replaceHd, false /* aImplicit */);
2361 }
2362 }
2363 }
2364
2365 alock.release();
2366
2367 // whether we were successful or not, we need to set the machine
2368 // state and save the machine settings;
2369 {
2370 // preserve existing error info so that the result can
2371 // be properly reported to the progress object below
2372 ErrorInfoKeeper eik;
2373
2374 // restore the machine state that was saved when the
2375 // task was started
2376 setMachineState(aTask.machineStateBackup);
2377 updateMachineStateOnClient();
2378
2379 if (settingsChanged)
2380 {
2381 // saveSettings needs VirtualBox write lock in addition to our own
2382 // (parent -> child locking order!)
2383 AutoWriteLock vboxLock(mParent COMMA_LOCKVAL_SRC_POS);
2384 alock.acquire();
2385
2386 saveSettings(SaveS_InformCallbacksAnyway);
2387 }
2388 }
2389
2390 // report the result (this will try to fetch current error info on failure)
2391 aTask.pProgress->notifyComplete(rc);
2392
2393 if (SUCCEEDED(rc))
2394 mParent->onSnapshotDeleted(mData->mUuid, snapshotId1);
2395
2396 LogFlowThisFunc(("Done deleting snapshot (rc=%08X)\n", rc));
2397 LogFlowThisFuncLeave();
2398}
2399
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use