VirtualBox

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

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

Stop cancel operation succeeds ref 5903

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