VirtualBox

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

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

Main: change API. Interface IMediumFormat was touched. Attribute capabilities got the type MediumFormatCapabilities and option safeArray. PR5434.

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