VirtualBox

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

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

Main/Medium: Implement medium type Shareable, and along the way added a method to query the medium variant. Lots of terminology cleanup to make sure the error messages talk about "medium" instead of "hard disk", as the latter sometimes also showed up in errors about floppy/DVD images.

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