VirtualBox

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

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

Main: fix and clarify that ISnapshot::SetName and SetDescription automatically save the machine settings; API docs

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

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