VirtualBox

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

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

Main: implement sharing saved state files between snapshots and machines in 'saved' state; this dramatically speeds up restoring snapshots as well as taking snapshots of 'saved' machines

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