VirtualBox

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

Last change on this file since 28479 was 28401, checked in by vboxsync, 15 years ago

Main/Machine+Snapshot+Medium: Big medium locking cleanup and straightened up the responsibilities between Snapshot and Medium. Rewritten task handling and cleaned up task implementation for medium operations. Implemented IMedium::mergeTo (no way to call it via any frontend yet), making the method parameters similar to IMedium::cloneto. Plus lots of other minor cleanups.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 91.7 KB
Line 
1/* $Id: SnapshotImpl.cpp 28401 2010-04-16 09:14:54Z vboxsync $ */
2
3/** @file
4 *
5 * COM class implementation for Snapshot and SnapshotMachine in VBoxSVC.
6 */
7
8/*
9 * Copyright (C) 2006-2010 Sun Microsystems, Inc.
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 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
20 * Clara, CA 95054 USA or visit http://www.sun.com if you need
21 * additional information or have any questions.
22 */
23
24#include "Logging.h"
25#include "SnapshotImpl.h"
26
27#include "MachineImpl.h"
28#include "MediumImpl.h"
29#include "Global.h"
30#include "ProgressImpl.h"
31
32// @todo these three includes are required for about one or two lines, try
33// to remove them and put that code in shared code in MachineImplcpp
34#include "SharedFolderImpl.h"
35#include "USBControllerImpl.h"
36#include "VirtualBoxImpl.h"
37
38#include "AutoCaller.h"
39
40#include <iprt/path.h>
41#include <VBox/param.h>
42#include <VBox/err.h>
43
44#include <VBox/settings.h>
45
46////////////////////////////////////////////////////////////////////////////////
47//
48// Globals
49//
50////////////////////////////////////////////////////////////////////////////////
51
52/**
53 * Progress callback handler for lengthy operations
54 * (corresponds to the FNRTPROGRESS typedef).
55 *
56 * @param uPercentage Completetion precentage (0-100).
57 * @param pvUser Pointer to the Progress instance.
58 */
59static DECLCALLBACK(int) progressCallback(unsigned uPercentage, void *pvUser)
60{
61 IProgress *progress = static_cast<IProgress*>(pvUser);
62
63 /* update the progress object */
64 if (progress)
65 progress->SetCurrentOperationProgress(uPercentage);
66
67 return VINF_SUCCESS;
68}
69
70////////////////////////////////////////////////////////////////////////////////
71//
72// Snapshot private data definition
73//
74////////////////////////////////////////////////////////////////////////////////
75
76typedef std::list< ComObjPtr<Snapshot> > SnapshotsList;
77
78struct Snapshot::Data
79{
80 Data()
81 : pVirtualBox(NULL)
82 {
83 RTTimeSpecSetMilli(&timeStamp, 0);
84 };
85
86 ~Data()
87 {}
88
89 const Guid uuid;
90 Utf8Str strName;
91 Utf8Str strDescription;
92 RTTIMESPEC timeStamp;
93 ComObjPtr<SnapshotMachine> pMachine;
94
95 /** weak VirtualBox parent */
96 VirtualBox * const pVirtualBox;
97
98 // pParent and llChildren are protected by Machine::snapshotsTreeLockHandle()
99 ComObjPtr<Snapshot> pParent;
100 SnapshotsList llChildren;
101};
102
103////////////////////////////////////////////////////////////////////////////////
104//
105// Constructor / destructor
106//
107////////////////////////////////////////////////////////////////////////////////
108
109HRESULT Snapshot::FinalConstruct()
110{
111 LogFlowThisFunc(("\n"));
112 return S_OK;
113}
114
115void Snapshot::FinalRelease()
116{
117 LogFlowThisFunc(("\n"));
118 uninit();
119}
120
121/**
122 * Initializes the instance
123 *
124 * @param aId id of the snapshot
125 * @param aName name of the snapshot
126 * @param aDescription name of the snapshot (NULL if no description)
127 * @param aTimeStamp timestamp of the snapshot, in ms since 1970-01-01 UTC
128 * @param aMachine machine associated with this snapshot
129 * @param aParent parent snapshot (NULL if no parent)
130 */
131HRESULT Snapshot::init(VirtualBox *aVirtualBox,
132 const Guid &aId,
133 const Utf8Str &aName,
134 const Utf8Str &aDescription,
135 const RTTIMESPEC &aTimeStamp,
136 SnapshotMachine *aMachine,
137 Snapshot *aParent)
138{
139 LogFlowThisFunc(("uuid=%s aParent->uuid=%s\n", aId.toString().c_str(), (aParent) ? aParent->m->uuid.toString().c_str() : ""));
140
141 ComAssertRet(!aId.isEmpty() && !aName.isEmpty() && aMachine, E_INVALIDARG);
142
143 /* Enclose the state transition NotReady->InInit->Ready */
144 AutoInitSpan autoInitSpan(this);
145 AssertReturn(autoInitSpan.isOk(), E_FAIL);
146
147 m = new Data;
148
149 /* share parent weakly */
150 unconst(m->pVirtualBox) = aVirtualBox;
151
152 m->pParent = aParent;
153
154 unconst(m->uuid) = aId;
155 m->strName = aName;
156 m->strDescription = aDescription;
157 m->timeStamp = aTimeStamp;
158 m->pMachine = aMachine;
159
160 if (aParent)
161 aParent->m->llChildren.push_back(this);
162
163 /* Confirm a successful initialization when it's the case */
164 autoInitSpan.setSucceeded();
165
166 return S_OK;
167}
168
169/**
170 * Uninitializes the instance and sets the ready flag to FALSE.
171 * Called either from FinalRelease(), by the parent when it gets destroyed,
172 * or by a third party when it decides this object is no more valid.
173 *
174 * Since this manipulates the snapshots tree, the caller must hold the
175 * machine lock in write mode (which protects the snapshots tree)!
176 */
177void Snapshot::uninit()
178{
179 LogFlowThisFunc(("\n"));
180
181 /* Enclose the state transition Ready->InUninit->NotReady */
182 AutoUninitSpan autoUninitSpan(this);
183 if (autoUninitSpan.uninitDone())
184 return;
185
186 Assert(m->pMachine->isWriteLockOnCurrentThread());
187
188 // uninit all children
189 SnapshotsList::iterator it;
190 for (it = m->llChildren.begin();
191 it != m->llChildren.end();
192 ++it)
193 {
194 Snapshot *pChild = *it;
195 pChild->m->pParent.setNull();
196 pChild->uninit();
197 }
198 m->llChildren.clear(); // this unsets all the ComPtrs and probably calls delete
199
200 if (m->pParent)
201 deparent();
202
203 if (m->pMachine)
204 {
205 m->pMachine->uninit();
206 m->pMachine.setNull();
207 }
208
209 delete m;
210 m = NULL;
211}
212
213/**
214 * Delete the current snapshot by removing it from the tree of snapshots
215 * and reparenting its children.
216 *
217 * After this, the caller must call uninit() on the snapshot. We can't call
218 * that from here because if we do, the AutoUninitSpan waits forever for
219 * the number of callers to become 0 (it is 1 because of the AutoCaller in here).
220 *
221 * NOTE: this does NOT lock the snapshot, it is assumed that the machine state
222 * (and the snapshots tree) is protected by the caller having requested the machine
223 * lock in write mode AND the machine state must be DeletingSnapshot.
224 */
225void Snapshot::beginSnapshotDelete()
226{
227 AutoCaller autoCaller(this);
228 if (FAILED(autoCaller.rc()))
229 return;
230
231 // caller must have acquired the machine's write lock
232 Assert(m->pMachine->mData->mMachineState == MachineState_DeletingSnapshot);
233 Assert(m->pMachine->isWriteLockOnCurrentThread());
234
235 // the snapshot must have only one child when being deleted or no children at all
236 AssertReturnVoid(m->llChildren.size() <= 1);
237
238 ComObjPtr<Snapshot> parentSnapshot = m->pParent;
239
240 /// @todo (dmik):
241 // when we introduce clones later, deleting the snapshot will affect
242 // the current and first snapshots of clones, if they are direct children
243 // of this snapshot. So we will need to lock machines associated with
244 // child snapshots as well and update mCurrentSnapshot and/or
245 // mFirstSnapshot fields.
246
247 if (this == m->pMachine->mData->mCurrentSnapshot)
248 {
249 m->pMachine->mData->mCurrentSnapshot = parentSnapshot;
250
251 /* we've changed the base of the current state so mark it as
252 * modified as it no longer guaranteed to be its copy */
253 m->pMachine->mData->mCurrentStateModified = TRUE;
254 }
255
256 if (this == m->pMachine->mData->mFirstSnapshot)
257 {
258 if (m->llChildren.size() == 1)
259 {
260 ComObjPtr<Snapshot> childSnapshot = m->llChildren.front();
261 m->pMachine->mData->mFirstSnapshot = childSnapshot;
262 }
263 else
264 m->pMachine->mData->mFirstSnapshot.setNull();
265 }
266
267 // reparent our children
268 for (SnapshotsList::const_iterator it = m->llChildren.begin();
269 it != m->llChildren.end();
270 ++it)
271 {
272 ComObjPtr<Snapshot> child = *it;
273 // no need to lock, snapshots tree is protected by machine lock
274 child->m->pParent = m->pParent;
275 if (m->pParent)
276 m->pParent->m->llChildren.push_back(child);
277 }
278
279 // clear our own children list (since we reparented the children)
280 m->llChildren.clear();
281}
282
283/**
284 * Internal helper that removes "this" from the list of children of its
285 * parent. Used in uninit() and other places when reparenting is necessary.
286 *
287 * The caller must hold the machine lock in write mode (which protects the snapshots tree)!
288 */
289void Snapshot::deparent()
290{
291 Assert(m->pMachine->isWriteLockOnCurrentThread());
292
293 SnapshotsList &llParent = m->pParent->m->llChildren;
294 for (SnapshotsList::iterator it = llParent.begin();
295 it != llParent.end();
296 ++it)
297 {
298 Snapshot *pParentsChild = *it;
299 if (this == pParentsChild)
300 {
301 llParent.erase(it);
302 break;
303 }
304 }
305
306 m->pParent.setNull();
307}
308
309////////////////////////////////////////////////////////////////////////////////
310//
311// ISnapshot public methods
312//
313////////////////////////////////////////////////////////////////////////////////
314
315STDMETHODIMP Snapshot::COMGETTER(Id)(BSTR *aId)
316{
317 CheckComArgOutPointerValid(aId);
318
319 AutoCaller autoCaller(this);
320 if (FAILED(autoCaller.rc())) return autoCaller.rc();
321
322 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
323
324 m->uuid.toUtf16().cloneTo(aId);
325 return S_OK;
326}
327
328STDMETHODIMP Snapshot::COMGETTER(Name)(BSTR *aName)
329{
330 CheckComArgOutPointerValid(aName);
331
332 AutoCaller autoCaller(this);
333 if (FAILED(autoCaller.rc())) return autoCaller.rc();
334
335 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
336
337 m->strName.cloneTo(aName);
338 return S_OK;
339}
340
341/**
342 * @note Locks this object for writing, then calls Machine::onSnapshotChange()
343 * (see its lock requirements).
344 */
345STDMETHODIMP Snapshot::COMSETTER(Name)(IN_BSTR aName)
346{
347 CheckComArgStrNotEmptyOrNull(aName);
348
349 AutoCaller autoCaller(this);
350 if (FAILED(autoCaller.rc())) return autoCaller.rc();
351
352 Utf8Str strName(aName);
353
354 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
355
356 if (m->strName != strName)
357 {
358 m->strName = strName;
359
360 alock.leave(); /* Important! (child->parent locks are forbidden) */
361
362 return m->pMachine->onSnapshotChange(this);
363 }
364
365 return S_OK;
366}
367
368STDMETHODIMP Snapshot::COMGETTER(Description)(BSTR *aDescription)
369{
370 CheckComArgOutPointerValid(aDescription);
371
372 AutoCaller autoCaller(this);
373 if (FAILED(autoCaller.rc())) return autoCaller.rc();
374
375 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
376
377 m->strDescription.cloneTo(aDescription);
378 return S_OK;
379}
380
381STDMETHODIMP Snapshot::COMSETTER(Description)(IN_BSTR aDescription)
382{
383 AutoCaller autoCaller(this);
384 if (FAILED(autoCaller.rc())) return autoCaller.rc();
385
386 Utf8Str strDescription(aDescription);
387
388 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
389
390 if (m->strDescription != strDescription)
391 {
392 m->strDescription = strDescription;
393
394 alock.leave(); /* Important! (child->parent locks are forbidden) */
395
396 return m->pMachine->onSnapshotChange(this);
397 }
398
399 return S_OK;
400}
401
402STDMETHODIMP Snapshot::COMGETTER(TimeStamp)(LONG64 *aTimeStamp)
403{
404 CheckComArgOutPointerValid(aTimeStamp);
405
406 AutoCaller autoCaller(this);
407 if (FAILED(autoCaller.rc())) return autoCaller.rc();
408
409 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
410
411 *aTimeStamp = RTTimeSpecGetMilli(&m->timeStamp);
412 return S_OK;
413}
414
415STDMETHODIMP Snapshot::COMGETTER(Online)(BOOL *aOnline)
416{
417 CheckComArgOutPointerValid(aOnline);
418
419 AutoCaller autoCaller(this);
420 if (FAILED(autoCaller.rc())) return autoCaller.rc();
421
422 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
423
424 *aOnline = !stateFilePath().isEmpty();
425 return S_OK;
426}
427
428STDMETHODIMP Snapshot::COMGETTER(Machine)(IMachine **aMachine)
429{
430 CheckComArgOutPointerValid(aMachine);
431
432 AutoCaller autoCaller(this);
433 if (FAILED(autoCaller.rc())) return autoCaller.rc();
434
435 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
436
437 m->pMachine.queryInterfaceTo(aMachine);
438 return S_OK;
439}
440
441STDMETHODIMP Snapshot::COMGETTER(Parent)(ISnapshot **aParent)
442{
443 CheckComArgOutPointerValid(aParent);
444
445 AutoCaller autoCaller(this);
446 if (FAILED(autoCaller.rc())) return autoCaller.rc();
447
448 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
449
450 m->pParent.queryInterfaceTo(aParent);
451 return S_OK;
452}
453
454STDMETHODIMP Snapshot::COMGETTER(Children)(ComSafeArrayOut(ISnapshot *, aChildren))
455{
456 CheckComArgOutSafeArrayPointerValid(aChildren);
457
458 AutoCaller autoCaller(this);
459 if (FAILED(autoCaller.rc())) return autoCaller.rc();
460
461 // snapshots tree is protected by machine lock
462 AutoReadLock alock(m->pMachine COMMA_LOCKVAL_SRC_POS);
463
464 SafeIfaceArray<ISnapshot> collection(m->llChildren);
465 collection.detachTo(ComSafeArrayOutArg(aChildren));
466
467 return S_OK;
468}
469
470////////////////////////////////////////////////////////////////////////////////
471//
472// Snapshot public internal methods
473//
474////////////////////////////////////////////////////////////////////////////////
475
476/**
477 * Returns the parent snapshot or NULL if there's none. Must have caller + locking!
478 * @return
479 */
480const ComObjPtr<Snapshot>& Snapshot::getParent() const
481{
482 return m->pParent;
483}
484
485/**
486 * @note
487 * Must be called from under the object's lock!
488 */
489const Utf8Str& Snapshot::stateFilePath() const
490{
491 return m->pMachine->mSSData->mStateFilePath;
492}
493
494/**
495 * Returns the number of direct child snapshots, without grandchildren.
496 * Does not recurse.
497 * @return
498 */
499ULONG Snapshot::getChildrenCount()
500{
501 AutoCaller autoCaller(this);
502 AssertComRC(autoCaller.rc());
503
504 // snapshots tree is protected by machine lock
505 AutoReadLock alock(m->pMachine COMMA_LOCKVAL_SRC_POS);
506
507 return (ULONG)m->llChildren.size();
508}
509
510/**
511 * Implementation method for getAllChildrenCount() so we request the
512 * tree lock only once before recursing. Don't call directly.
513 * @return
514 */
515ULONG Snapshot::getAllChildrenCountImpl()
516{
517 AutoCaller autoCaller(this);
518 AssertComRC(autoCaller.rc());
519
520 ULONG count = (ULONG)m->llChildren.size();
521 for (SnapshotsList::const_iterator it = m->llChildren.begin();
522 it != m->llChildren.end();
523 ++it)
524 {
525 count += (*it)->getAllChildrenCountImpl();
526 }
527
528 return count;
529}
530
531/**
532 * Returns the number of child snapshots including all grandchildren.
533 * Recurses into the snapshots tree.
534 * @return
535 */
536ULONG Snapshot::getAllChildrenCount()
537{
538 AutoCaller autoCaller(this);
539 AssertComRC(autoCaller.rc());
540
541 // snapshots tree is protected by machine lock
542 AutoReadLock alock(m->pMachine COMMA_LOCKVAL_SRC_POS);
543
544 return getAllChildrenCountImpl();
545}
546
547/**
548 * Returns the SnapshotMachine that this snapshot belongs to.
549 * Caller must hold the snapshot's object lock!
550 * @return
551 */
552const ComObjPtr<SnapshotMachine>& Snapshot::getSnapshotMachine() const
553{
554 return m->pMachine;
555}
556
557/**
558 * Returns the UUID of this snapshot.
559 * Caller must hold the snapshot's object lock!
560 * @return
561 */
562Guid Snapshot::getId() const
563{
564 return m->uuid;
565}
566
567/**
568 * Returns the name of this snapshot.
569 * Caller must hold the snapshot's object lock!
570 * @return
571 */
572const Utf8Str& Snapshot::getName() const
573{
574 return m->strName;
575}
576
577/**
578 * Returns the time stamp of this snapshot.
579 * Caller must hold the snapshot's object lock!
580 * @return
581 */
582RTTIMESPEC Snapshot::getTimeStamp() const
583{
584 return m->timeStamp;
585}
586
587/**
588 * Searches for a snapshot with the given ID among children, grand-children,
589 * etc. of this snapshot. This snapshot itself is also included in the search.
590 *
591 * Caller must hold the machine lock (which protects the snapshots tree!)
592 */
593ComObjPtr<Snapshot> Snapshot::findChildOrSelf(IN_GUID aId)
594{
595 ComObjPtr<Snapshot> child;
596
597 AutoCaller autoCaller(this);
598 AssertComRC(autoCaller.rc());
599
600 // no need to lock, uuid is const
601 if (m->uuid == aId)
602 child = this;
603 else
604 {
605 for (SnapshotsList::const_iterator it = m->llChildren.begin();
606 it != m->llChildren.end();
607 ++it)
608 {
609 if ((child = (*it)->findChildOrSelf(aId)))
610 break;
611 }
612 }
613
614 return child;
615}
616
617/**
618 * Searches for a first snapshot with the given name among children,
619 * grand-children, etc. of this snapshot. This snapshot itself is also included
620 * in the search.
621 *
622 * Caller must hold the machine lock (which protects the snapshots tree!)
623 */
624ComObjPtr<Snapshot> Snapshot::findChildOrSelf(const Utf8Str &aName)
625{
626 ComObjPtr<Snapshot> child;
627 AssertReturn(!aName.isEmpty(), child);
628
629 AutoCaller autoCaller(this);
630 AssertComRC(autoCaller.rc());
631
632 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
633
634 if (m->strName == aName)
635 child = this;
636 else
637 {
638 alock.release();
639 for (SnapshotsList::const_iterator it = m->llChildren.begin();
640 it != m->llChildren.end();
641 ++it)
642 {
643 if ((child = (*it)->findChildOrSelf(aName)))
644 break;
645 }
646 }
647
648 return child;
649}
650
651/**
652 * Internal implementation for Snapshot::updateSavedStatePaths (below).
653 * @param aOldPath
654 * @param aNewPath
655 */
656void Snapshot::updateSavedStatePathsImpl(const char *aOldPath, const char *aNewPath)
657{
658 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
659
660 const Utf8Str &path = m->pMachine->mSSData->mStateFilePath;
661 LogFlowThisFunc(("Snap[%s].statePath={%s}\n", m->strName.c_str(), path.c_str()));
662
663 /* state file may be NULL (for offline snapshots) */
664 if ( path.length()
665 && RTPathStartsWith(path.c_str(), aOldPath)
666 )
667 {
668 m->pMachine->mSSData->mStateFilePath = Utf8StrFmt("%s%s", aNewPath, path.raw() + strlen(aOldPath));
669
670 LogFlowThisFunc(("-> updated: {%s}\n", path.raw()));
671 }
672
673 for (SnapshotsList::const_iterator it = m->llChildren.begin();
674 it != m->llChildren.end();
675 ++it)
676 {
677 Snapshot *pChild = *it;
678 pChild->updateSavedStatePathsImpl(aOldPath, aNewPath);
679 }
680}
681
682/**
683 * Checks if the specified path change affects the saved state file path of
684 * this snapshot or any of its (grand-)children and updates it accordingly.
685 *
686 * Intended to be called by Machine::openConfigLoader() only.
687 *
688 * @param aOldPath old path (full)
689 * @param aNewPath new path (full)
690 *
691 * @note Locks the machine (for the snapshots tree) + this object + children for writing.
692 */
693void Snapshot::updateSavedStatePaths(const char *aOldPath, const char *aNewPath)
694{
695 LogFlowThisFunc(("aOldPath={%s} aNewPath={%s}\n", aOldPath, aNewPath));
696
697 AssertReturnVoid(aOldPath);
698 AssertReturnVoid(aNewPath);
699
700 AutoCaller autoCaller(this);
701 AssertComRC(autoCaller.rc());
702
703 // snapshots tree is protected by machine lock
704 AutoWriteLock alock(m->pMachine COMMA_LOCKVAL_SRC_POS);
705
706 // call the implementation under the tree lock
707 updateSavedStatePathsImpl(aOldPath, aNewPath);
708}
709
710/**
711 * Internal implementation for Snapshot::saveSnapshot (below). Caller has
712 * requested the snapshots tree (machine) lock.
713 *
714 * @param aNode
715 * @param aAttrsOnly
716 * @return
717 */
718HRESULT Snapshot::saveSnapshotImpl(settings::Snapshot &data, bool aAttrsOnly)
719{
720 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
721
722 data.uuid = m->uuid;
723 data.strName = m->strName;
724 data.timestamp = m->timeStamp;
725 data.strDescription = m->strDescription;
726
727 if (aAttrsOnly)
728 return S_OK;
729
730 /* stateFile (optional) */
731 if (!stateFilePath().isEmpty())
732 /* try to make the file name relative to the settings file dir */
733 m->pMachine->calculateRelativePath(stateFilePath(), data.strStateFile);
734 else
735 data.strStateFile.setNull();
736
737 HRESULT rc = m->pMachine->saveHardware(data.hardware);
738 if (FAILED(rc)) return rc;
739
740 rc = m->pMachine->saveStorageControllers(data.storage);
741 if (FAILED(rc)) return rc;
742
743 alock.release();
744
745 data.llChildSnapshots.clear();
746
747 if (m->llChildren.size())
748 {
749 for (SnapshotsList::const_iterator it = m->llChildren.begin();
750 it != m->llChildren.end();
751 ++it)
752 {
753 settings::Snapshot snap;
754 rc = (*it)->saveSnapshotImpl(snap, aAttrsOnly);
755 if (FAILED(rc)) return rc;
756
757 data.llChildSnapshots.push_back(snap);
758 }
759 }
760
761 return S_OK;
762}
763
764/**
765 * Saves the given snapshot and all its children (unless \a aAttrsOnly is true).
766 * It is assumed that the given node is empty (unless \a aAttrsOnly is true).
767 *
768 * @param aNode <Snapshot> node to save the snapshot to.
769 * @param aSnapshot Snapshot to save.
770 * @param aAttrsOnly If true, only updatge user-changeable attrs.
771 */
772HRESULT Snapshot::saveSnapshot(settings::Snapshot &data, bool aAttrsOnly)
773{
774 // snapshots tree is protected by machine lock
775 AutoReadLock alock(m->pMachine COMMA_LOCKVAL_SRC_POS);
776
777 return saveSnapshotImpl(data, aAttrsOnly);
778}
779
780////////////////////////////////////////////////////////////////////////////////
781//
782// SnapshotMachine implementation
783//
784////////////////////////////////////////////////////////////////////////////////
785
786DEFINE_EMPTY_CTOR_DTOR(SnapshotMachine)
787
788HRESULT SnapshotMachine::FinalConstruct()
789{
790 LogFlowThisFunc(("\n"));
791
792 return S_OK;
793}
794
795void SnapshotMachine::FinalRelease()
796{
797 LogFlowThisFunc(("\n"));
798
799 uninit();
800}
801
802/**
803 * Initializes the SnapshotMachine object when taking a snapshot.
804 *
805 * @param aSessionMachine machine to take a snapshot from
806 * @param aSnapshotId snapshot ID of this snapshot machine
807 * @param aStateFilePath file where the execution state will be later saved
808 * (or NULL for the offline snapshot)
809 *
810 * @note The aSessionMachine must be locked for writing.
811 */
812HRESULT SnapshotMachine::init(SessionMachine *aSessionMachine,
813 IN_GUID aSnapshotId,
814 const Utf8Str &aStateFilePath)
815{
816 LogFlowThisFuncEnter();
817 LogFlowThisFunc(("mName={%ls}\n", aSessionMachine->mUserData->mName.raw()));
818
819 AssertReturn(aSessionMachine && !Guid(aSnapshotId).isEmpty(), E_INVALIDARG);
820
821 /* Enclose the state transition NotReady->InInit->Ready */
822 AutoInitSpan autoInitSpan(this);
823 AssertReturn(autoInitSpan.isOk(), E_FAIL);
824
825 AssertReturn(aSessionMachine->isWriteLockOnCurrentThread(), E_FAIL);
826
827 mSnapshotId = aSnapshotId;
828
829 /* memorize the primary Machine instance (i.e. not SessionMachine!) */
830 unconst(mPeer) = aSessionMachine->mPeer;
831 /* share the parent pointer */
832 unconst(mParent) = mPeer->mParent;
833
834 /* take the pointer to Data to share */
835 mData.share(mPeer->mData);
836
837 /* take the pointer to UserData to share (our UserData must always be the
838 * same as Machine's data) */
839 mUserData.share(mPeer->mUserData);
840 /* make a private copy of all other data (recent changes from SessionMachine) */
841 mHWData.attachCopy(aSessionMachine->mHWData);
842 mMediaData.attachCopy(aSessionMachine->mMediaData);
843
844 /* SSData is always unique for SnapshotMachine */
845 mSSData.allocate();
846 mSSData->mStateFilePath = aStateFilePath;
847
848 HRESULT rc = S_OK;
849
850 /* create copies of all shared folders (mHWData after attiching a copy
851 * contains just references to original objects) */
852 for (HWData::SharedFolderList::iterator it = mHWData->mSharedFolders.begin();
853 it != mHWData->mSharedFolders.end();
854 ++it)
855 {
856 ComObjPtr<SharedFolder> folder;
857 folder.createObject();
858 rc = folder->initCopy(this, *it);
859 if (FAILED(rc)) return rc;
860 *it = folder;
861 }
862
863 /* associate hard disks with the snapshot
864 * (Machine::uninitDataAndChildObjects() will deassociate at destruction) */
865 for (MediaData::AttachmentList::const_iterator it = mMediaData->mAttachments.begin();
866 it != mMediaData->mAttachments.end();
867 ++it)
868 {
869 MediumAttachment *pAtt = *it;
870 Medium *pMedium = pAtt->getMedium();
871 if (pMedium) // can be NULL for non-harddisk
872 {
873 rc = pMedium->attachTo(mData->mUuid, mSnapshotId);
874 AssertComRC(rc);
875 }
876 }
877
878 /* create copies of all storage controllers (mStorageControllerData
879 * after attaching a copy contains just references to original objects) */
880 mStorageControllers.allocate();
881 for (StorageControllerList::const_iterator
882 it = aSessionMachine->mStorageControllers->begin();
883 it != aSessionMachine->mStorageControllers->end();
884 ++it)
885 {
886 ComObjPtr<StorageController> ctrl;
887 ctrl.createObject();
888 ctrl->initCopy(this, *it);
889 mStorageControllers->push_back(ctrl);
890 }
891
892 /* create all other child objects that will be immutable private copies */
893
894 unconst(mBIOSSettings).createObject();
895 mBIOSSettings->initCopy(this, mPeer->mBIOSSettings);
896
897#ifdef VBOX_WITH_VRDP
898 unconst(mVRDPServer).createObject();
899 mVRDPServer->initCopy(this, mPeer->mVRDPServer);
900#endif
901
902 unconst(mAudioAdapter).createObject();
903 mAudioAdapter->initCopy(this, mPeer->mAudioAdapter);
904
905 unconst(mUSBController).createObject();
906 mUSBController->initCopy(this, mPeer->mUSBController);
907
908 for (ULONG slot = 0; slot < RT_ELEMENTS(mNetworkAdapters); slot++)
909 {
910 unconst(mNetworkAdapters[slot]).createObject();
911 mNetworkAdapters[slot]->initCopy(this, mPeer->mNetworkAdapters[slot]);
912 }
913
914 for (ULONG slot = 0; slot < RT_ELEMENTS(mSerialPorts); slot++)
915 {
916 unconst(mSerialPorts[slot]).createObject();
917 mSerialPorts[slot]->initCopy(this, mPeer->mSerialPorts[slot]);
918 }
919
920 for (ULONG slot = 0; slot < RT_ELEMENTS(mParallelPorts); slot++)
921 {
922 unconst(mParallelPorts[slot]).createObject();
923 mParallelPorts[slot]->initCopy(this, mPeer->mParallelPorts[slot]);
924 }
925
926 /* Confirm a successful initialization when it's the case */
927 autoInitSpan.setSucceeded();
928
929 LogFlowThisFuncLeave();
930 return S_OK;
931}
932
933/**
934 * Initializes the SnapshotMachine object when loading from the settings file.
935 *
936 * @param aMachine machine the snapshot belngs to
937 * @param aHWNode <Hardware> node
938 * @param aHDAsNode <HardDiskAttachments> node
939 * @param aSnapshotId snapshot ID of this snapshot machine
940 * @param aStateFilePath file where the execution state is saved
941 * (or NULL for the offline snapshot)
942 *
943 * @note Doesn't lock anything.
944 */
945HRESULT SnapshotMachine::init(Machine *aMachine,
946 const settings::Hardware &hardware,
947 const settings::Storage &storage,
948 IN_GUID aSnapshotId,
949 const Utf8Str &aStateFilePath)
950{
951 LogFlowThisFuncEnter();
952 LogFlowThisFunc(("mName={%ls}\n", aMachine->mUserData->mName.raw()));
953
954 AssertReturn(aMachine && !Guid(aSnapshotId).isEmpty(), E_INVALIDARG);
955
956 /* Enclose the state transition NotReady->InInit->Ready */
957 AutoInitSpan autoInitSpan(this);
958 AssertReturn(autoInitSpan.isOk(), E_FAIL);
959
960 /* Don't need to lock aMachine when VirtualBox is starting up */
961
962 mSnapshotId = aSnapshotId;
963
964 /* memorize the primary Machine instance */
965 unconst(mPeer) = aMachine;
966 /* share the parent pointer */
967 unconst(mParent) = mPeer->mParent;
968
969 /* take the pointer to Data to share */
970 mData.share(mPeer->mData);
971 /*
972 * take the pointer to UserData to share
973 * (our UserData must always be the same as Machine's data)
974 */
975 mUserData.share(mPeer->mUserData);
976 /* allocate private copies of all other data (will be loaded from settings) */
977 mHWData.allocate();
978 mMediaData.allocate();
979 mStorageControllers.allocate();
980
981 /* SSData is always unique for SnapshotMachine */
982 mSSData.allocate();
983 mSSData->mStateFilePath = aStateFilePath;
984
985 /* create all other child objects that will be immutable private copies */
986
987 unconst(mBIOSSettings).createObject();
988 mBIOSSettings->init(this);
989
990#ifdef VBOX_WITH_VRDP
991 unconst(mVRDPServer).createObject();
992 mVRDPServer->init(this);
993#endif
994
995 unconst(mAudioAdapter).createObject();
996 mAudioAdapter->init(this);
997
998 unconst(mUSBController).createObject();
999 mUSBController->init(this);
1000
1001 for (ULONG slot = 0; slot < RT_ELEMENTS(mNetworkAdapters); slot++)
1002 {
1003 unconst(mNetworkAdapters[slot]).createObject();
1004 mNetworkAdapters[slot]->init(this, slot);
1005 }
1006
1007 for (ULONG slot = 0; slot < RT_ELEMENTS(mSerialPorts); slot++)
1008 {
1009 unconst(mSerialPorts[slot]).createObject();
1010 mSerialPorts[slot]->init(this, slot);
1011 }
1012
1013 for (ULONG slot = 0; slot < RT_ELEMENTS(mParallelPorts); slot++)
1014 {
1015 unconst(mParallelPorts[slot]).createObject();
1016 mParallelPorts[slot]->init(this, slot);
1017 }
1018
1019 /* load hardware and harddisk settings */
1020
1021 HRESULT rc = loadHardware(hardware);
1022 if (SUCCEEDED(rc))
1023 rc = loadStorageControllers(storage, &mSnapshotId);
1024
1025 if (SUCCEEDED(rc))
1026 /* commit all changes made during the initialization */
1027 commit(); // @todo r=dj why do we need a commit in init?!? this is very expensive
1028
1029 /* Confirm a successful initialization when it's the case */
1030 if (SUCCEEDED(rc))
1031 autoInitSpan.setSucceeded();
1032
1033 LogFlowThisFuncLeave();
1034 return rc;
1035}
1036
1037/**
1038 * Uninitializes this SnapshotMachine object.
1039 */
1040void SnapshotMachine::uninit()
1041{
1042 LogFlowThisFuncEnter();
1043
1044 /* Enclose the state transition Ready->InUninit->NotReady */
1045 AutoUninitSpan autoUninitSpan(this);
1046 if (autoUninitSpan.uninitDone())
1047 return;
1048
1049 uninitDataAndChildObjects();
1050
1051 /* free the essential data structure last */
1052 mData.free();
1053
1054 unconst(mParent) = NULL;
1055 unconst(mPeer) = NULL;
1056
1057 LogFlowThisFuncLeave();
1058}
1059
1060/**
1061 * Overrides VirtualBoxBase::lockHandle() in order to share the lock handle
1062 * with the primary Machine instance (mPeer).
1063 */
1064RWLockHandle *SnapshotMachine::lockHandle() const
1065{
1066 AssertReturn(mPeer != NULL, NULL);
1067 return mPeer->lockHandle();
1068}
1069
1070////////////////////////////////////////////////////////////////////////////////
1071//
1072// SnapshotMachine public internal methods
1073//
1074////////////////////////////////////////////////////////////////////////////////
1075
1076/**
1077 * Called by the snapshot object associated with this SnapshotMachine when
1078 * snapshot data such as name or description is changed.
1079 *
1080 * @note Locks this object for writing.
1081 */
1082HRESULT SnapshotMachine::onSnapshotChange(Snapshot *aSnapshot)
1083{
1084 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1085
1086 // mPeer->saveAllSnapshots(); @todo
1087
1088 /* inform callbacks */
1089 mParent->onSnapshotChange(mData->mUuid, aSnapshot->getId());
1090
1091 return S_OK;
1092}
1093
1094////////////////////////////////////////////////////////////////////////////////
1095//
1096// SessionMachine task records
1097//
1098////////////////////////////////////////////////////////////////////////////////
1099
1100/**
1101 * Abstract base class for SessionMachine::RestoreSnapshotTask and
1102 * SessionMachine::DeleteSnapshotTask. This is necessary since
1103 * RTThreadCreate cannot call a method as its thread function, so
1104 * instead we have it call the static SessionMachine::taskHandler,
1105 * which can then call the handler() method in here (implemented
1106 * by the children).
1107 */
1108struct SessionMachine::SnapshotTask
1109{
1110 SnapshotTask(SessionMachine *m,
1111 Progress *p,
1112 Snapshot *s)
1113 : pMachine(m),
1114 pProgress(p),
1115 machineStateBackup(m->mData->mMachineState), // save the current machine state
1116 pSnapshot(s)
1117 {}
1118
1119 void modifyBackedUpState(MachineState_T s)
1120 {
1121 *const_cast<MachineState_T*>(&machineStateBackup) = s;
1122 }
1123
1124 virtual void handler() = 0;
1125
1126 ComObjPtr<SessionMachine> pMachine;
1127 ComObjPtr<Progress> pProgress;
1128 const MachineState_T machineStateBackup;
1129 ComObjPtr<Snapshot> pSnapshot;
1130};
1131
1132/** Restore snapshot state task */
1133struct SessionMachine::RestoreSnapshotTask
1134 : public SessionMachine::SnapshotTask
1135{
1136 RestoreSnapshotTask(SessionMachine *m,
1137 Progress *p,
1138 Snapshot *s,
1139 ULONG ulStateFileSizeMB)
1140 : SnapshotTask(m, p, s),
1141 m_ulStateFileSizeMB(ulStateFileSizeMB)
1142 {}
1143
1144 void handler()
1145 {
1146 pMachine->restoreSnapshotHandler(*this);
1147 }
1148
1149 ULONG m_ulStateFileSizeMB;
1150};
1151
1152/** Delete snapshot task */
1153struct SessionMachine::DeleteSnapshotTask
1154 : public SessionMachine::SnapshotTask
1155{
1156 DeleteSnapshotTask(SessionMachine *m,
1157 Progress *p,
1158 Snapshot *s)
1159 : SnapshotTask(m, p, s)
1160 {}
1161
1162 void handler()
1163 {
1164 pMachine->deleteSnapshotHandler(*this);
1165 }
1166
1167private:
1168 DeleteSnapshotTask(const SnapshotTask &task)
1169 : SnapshotTask(task)
1170 {}
1171};
1172
1173/**
1174 * Static SessionMachine method that can get passed to RTThreadCreate to
1175 * have a thread started for a SnapshotTask. See SnapshotTask above.
1176 *
1177 * This calls either RestoreSnapshotTask::handler() or DeleteSnapshotTask::handler().
1178 */
1179
1180/* static */ DECLCALLBACK(int) SessionMachine::taskHandler(RTTHREAD /* thread */, void *pvUser)
1181{
1182 AssertReturn(pvUser, VERR_INVALID_POINTER);
1183
1184 SnapshotTask *task = static_cast<SnapshotTask*>(pvUser);
1185 task->handler();
1186
1187 // it's our responsibility to delete the task
1188 delete task;
1189
1190 return 0;
1191}
1192
1193////////////////////////////////////////////////////////////////////////////////
1194//
1195// TakeSnapshot methods (SessionMachine and related tasks)
1196//
1197////////////////////////////////////////////////////////////////////////////////
1198
1199/**
1200 * Implementation for IInternalMachineControl::beginTakingSnapshot().
1201 *
1202 * Gets called indirectly from Console::TakeSnapshot, which creates a
1203 * progress object in the client and then starts a thread
1204 * (Console::fntTakeSnapshotWorker) which then calls this.
1205 *
1206 * In other words, the asynchronous work for taking snapshots takes place
1207 * on the _client_ (in the Console). This is different from restoring
1208 * or deleting snapshots, which start threads on the server.
1209 *
1210 * This does the server-side work of taking a snapshot: it creates diffencing
1211 * images for all hard disks attached to the machine and then creates a
1212 * Snapshot object with a corresponding SnapshotMachine to save the VM settings.
1213 *
1214 * The client's fntTakeSnapshotWorker() blocks while this takes place.
1215 * After this returns successfully, fntTakeSnapshotWorker() will begin
1216 * saving the machine state to the snapshot object and reconfigure the
1217 * hard disks.
1218 *
1219 * When the console is done, it calls SessionMachine::EndTakingSnapshot().
1220 *
1221 * @note Locks mParent + this object for writing.
1222 *
1223 * @param aInitiator in: The console on which Console::TakeSnapshot was called.
1224 * @param aName in: The name for the new snapshot.
1225 * @param aDescription in: A description for the new snapshot.
1226 * @param aConsoleProgress in: The console's (client's) progress object.
1227 * @param fTakingSnapshotOnline in: True if an online snapshot is being taken (i.e. machine is running).
1228 * @param aStateFilePath out: name of file in snapshots folder to which the console should write the VM state.
1229 * @return
1230 */
1231STDMETHODIMP SessionMachine::BeginTakingSnapshot(IConsole *aInitiator,
1232 IN_BSTR aName,
1233 IN_BSTR aDescription,
1234 IProgress *aConsoleProgress,
1235 BOOL fTakingSnapshotOnline,
1236 BSTR *aStateFilePath)
1237{
1238 LogFlowThisFuncEnter();
1239
1240 AssertReturn(aInitiator && aName, E_INVALIDARG);
1241 AssertReturn(aStateFilePath, E_POINTER);
1242
1243 LogFlowThisFunc(("aName='%ls' fTakingSnapshotOnline=%RTbool\n", aName, fTakingSnapshotOnline));
1244
1245 AutoCaller autoCaller(this);
1246 AssertComRCReturn(autoCaller.rc(), autoCaller.rc());
1247
1248 // if this becomes true, we need to call VirtualBox::saveSettings() in the end
1249 bool fNeedsSaveSettings = false;
1250
1251 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1252
1253 AssertReturn( !Global::IsOnlineOrTransient(mData->mMachineState)
1254 || mData->mMachineState == MachineState_Running
1255 || mData->mMachineState == MachineState_Paused, E_FAIL);
1256 AssertReturn(mSnapshotData.mLastState == MachineState_Null, E_FAIL);
1257 AssertReturn(mSnapshotData.mSnapshot.isNull(), E_FAIL);
1258
1259 if ( !fTakingSnapshotOnline
1260 && mData->mMachineState != MachineState_Saved
1261 )
1262 {
1263 /* save all current settings to ensure current changes are committed and
1264 * hard disks are fixed up */
1265 HRESULT rc = saveSettings(NULL);
1266 // no need to check for whether VirtualBox.xml needs changing since
1267 // we can't have a machine XML rename pending at this point
1268 if (FAILED(rc)) return rc;
1269 }
1270
1271 /* create an ID for the snapshot */
1272 Guid snapshotId;
1273 snapshotId.create();
1274
1275 Utf8Str strStateFilePath;
1276 /* stateFilePath is null when the machine is not online nor saved */
1277 if ( fTakingSnapshotOnline
1278 || mData->mMachineState == MachineState_Saved)
1279 {
1280 strStateFilePath = Utf8StrFmt("%ls%c{%RTuuid}.sav",
1281 mUserData->mSnapshotFolderFull.raw(),
1282 RTPATH_DELIMITER,
1283 snapshotId.ptr());
1284 /* ensure the directory for the saved state file exists */
1285 HRESULT rc = VirtualBox::ensureFilePathExists(strStateFilePath);
1286 if (FAILED(rc)) return rc;
1287 }
1288
1289 /* create a snapshot machine object */
1290 ComObjPtr<SnapshotMachine> snapshotMachine;
1291 snapshotMachine.createObject();
1292 HRESULT rc = snapshotMachine->init(this, snapshotId, strStateFilePath);
1293 AssertComRCReturn(rc, rc);
1294
1295 /* create a snapshot object */
1296 RTTIMESPEC time;
1297 ComObjPtr<Snapshot> pSnapshot;
1298 pSnapshot.createObject();
1299 rc = pSnapshot->init(mParent,
1300 snapshotId,
1301 aName,
1302 aDescription,
1303 *RTTimeNow(&time),
1304 snapshotMachine,
1305 mData->mCurrentSnapshot);
1306 AssertComRCReturnRC(rc);
1307
1308 /* fill in the snapshot data */
1309 mSnapshotData.mLastState = mData->mMachineState;
1310 mSnapshotData.mSnapshot = pSnapshot;
1311
1312 try
1313 {
1314 LogFlowThisFunc(("Creating differencing hard disks (online=%d)...\n",
1315 fTakingSnapshotOnline));
1316
1317 // backup the media data so we can recover if things goes wrong along the day;
1318 // the matching commit() is in fixupMedia() during endSnapshot()
1319 setModified(IsModified_Storage);
1320 mMediaData.backup();
1321
1322 /* Console::fntTakeSnapshotWorker and friends expects this. */
1323 if (mSnapshotData.mLastState == MachineState_Running)
1324 setMachineState(MachineState_LiveSnapshotting);
1325 else
1326 setMachineState(MachineState_Saving); /** @todo Confusing! Saving is used for both online and offline snapshots. */
1327
1328 /* create new differencing hard disks and attach them to this machine */
1329 rc = createImplicitDiffs(mUserData->mSnapshotFolderFull,
1330 aConsoleProgress,
1331 1, // operation weight; must be the same as in Console::TakeSnapshot()
1332 !!fTakingSnapshotOnline,
1333 &fNeedsSaveSettings);
1334 if (FAILED(rc))
1335 throw rc;
1336
1337 if (mSnapshotData.mLastState == MachineState_Saved)
1338 {
1339 Utf8Str stateFrom = mSSData->mStateFilePath;
1340 Utf8Str stateTo = mSnapshotData.mSnapshot->stateFilePath();
1341
1342 LogFlowThisFunc(("Copying the execution state from '%s' to '%s'...\n",
1343 stateFrom.raw(), stateTo.raw()));
1344
1345 aConsoleProgress->SetNextOperation(Bstr(tr("Copying the execution state")),
1346 1); // weight
1347
1348 /* Leave the lock before a lengthy operation (machine is protected
1349 * by "Saving" machine state now) */
1350 alock.release();
1351
1352 /* copy the state file */
1353 int vrc = RTFileCopyEx(stateFrom.c_str(),
1354 stateTo.c_str(),
1355 0,
1356 progressCallback,
1357 aConsoleProgress);
1358 alock.acquire();
1359
1360 if (RT_FAILURE(vrc))
1361 /** @todo r=bird: Delete stateTo when appropriate. */
1362 throw setError(E_FAIL,
1363 tr("Could not copy the state file '%s' to '%s' (%Rrc)"),
1364 stateFrom.raw(),
1365 stateTo.raw(),
1366 vrc);
1367 }
1368 }
1369 catch (HRESULT hrc)
1370 {
1371 LogThisFunc(("Caught %Rhrc [%s]\n", hrc, Global::stringifyMachineState(mData->mMachineState) ));
1372 if ( mSnapshotData.mLastState != mData->mMachineState
1373 && ( mSnapshotData.mLastState == MachineState_Running
1374 ? mData->mMachineState == MachineState_LiveSnapshotting
1375 : mData->mMachineState == MachineState_Saving)
1376 )
1377 setMachineState(mSnapshotData.mLastState);
1378
1379 pSnapshot->uninit();
1380 pSnapshot.setNull();
1381 mSnapshotData.mLastState = MachineState_Null;
1382 mSnapshotData.mSnapshot.setNull();
1383
1384 rc = hrc;
1385
1386 // @todo r=dj what with the implicit diff that we created above? this is never cleaned up
1387 }
1388
1389 if (fTakingSnapshotOnline && SUCCEEDED(rc))
1390 strStateFilePath.cloneTo(aStateFilePath);
1391 else
1392 *aStateFilePath = NULL;
1393
1394 // @todo r=dj normally we would need to save the settings if fNeedsSaveSettings was set to true,
1395 // but since we have no error handling that cleans up the diff image that might have gotten created,
1396 // there's no point in saving the disk registry at this point either... this needs fixing.
1397
1398 LogFlowThisFunc(("LEAVE - %Rhrc [%s]\n", rc, Global::stringifyMachineState(mData->mMachineState) ));
1399 return rc;
1400}
1401
1402/**
1403 * Implementation for IInternalMachineControl::endTakingSnapshot().
1404 *
1405 * Called by the Console when it's done saving the VM state into the snapshot
1406 * (if online) and reconfiguring the hard disks. See BeginTakingSnapshot() above.
1407 *
1408 * This also gets called if the console part of snapshotting failed after the
1409 * BeginTakingSnapshot() call, to clean up the server side.
1410 *
1411 * @note Locks VirtualBox and this object for writing.
1412 *
1413 * @param aSuccess Whether Console was successful with the client-side snapshot things.
1414 * @return
1415 */
1416STDMETHODIMP SessionMachine::EndTakingSnapshot(BOOL aSuccess)
1417{
1418 LogFlowThisFunc(("\n"));
1419
1420 AutoCaller autoCaller(this);
1421 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
1422
1423 AutoWriteLock machineLock(this COMMA_LOCKVAL_SRC_POS);
1424
1425 AssertReturn( !aSuccess
1426 || ( ( mData->mMachineState == MachineState_Saving
1427 || mData->mMachineState == MachineState_LiveSnapshotting)
1428 && mSnapshotData.mLastState != MachineState_Null
1429 && !mSnapshotData.mSnapshot.isNull()
1430 )
1431 , E_FAIL);
1432
1433 /*
1434 * Restore the state we had when BeginTakingSnapshot() was called,
1435 * Console::fntTakeSnapshotWorker restores its local copy when we return.
1436 * If the state was Running, then let Console::fntTakeSnapshotWorker do it
1437 * all to avoid races.
1438 */
1439 if ( mData->mMachineState != mSnapshotData.mLastState
1440 && mSnapshotData.mLastState != MachineState_Running
1441 )
1442 setMachineState(mSnapshotData.mLastState);
1443
1444 ComObjPtr<Snapshot> pOldFirstSnap = mData->mFirstSnapshot;
1445 ComObjPtr<Snapshot> pOldCurrentSnap = mData->mCurrentSnapshot;
1446
1447 bool fOnline = Global::IsOnline(mSnapshotData.mLastState);
1448
1449 HRESULT rc = S_OK;
1450
1451 if (aSuccess)
1452 {
1453 // new snapshot becomes the current one
1454 mData->mCurrentSnapshot = mSnapshotData.mSnapshot;
1455
1456 /* memorize the first snapshot if necessary */
1457 if (!mData->mFirstSnapshot)
1458 mData->mFirstSnapshot = mData->mCurrentSnapshot;
1459
1460 int flSaveSettings = SaveS_Force; // do not do a deep compare in machine settings,
1461 // snapshots change, so we know we need to save
1462 if (!fOnline)
1463 /* the machine was powered off or saved when taking a snapshot, so
1464 * reset the mCurrentStateModified flag */
1465 flSaveSettings |= SaveS_ResetCurStateModified;
1466
1467 rc = saveSettings(NULL, flSaveSettings);
1468 // no need to change for whether VirtualBox.xml needs saving since
1469 // we'll save the global settings below anyway
1470 }
1471
1472 if (aSuccess && SUCCEEDED(rc))
1473 {
1474 /* associate old hard disks with the snapshot and do locking/unlocking*/
1475 commitMedia(fOnline);
1476
1477 /* inform callbacks */
1478 mParent->onSnapshotTaken(mData->mUuid,
1479 mSnapshotData.mSnapshot->getId());
1480 }
1481 else
1482 {
1483 /* delete all differencing hard disks created (this will also attach
1484 * their parents back by rolling back mMediaData) */
1485 rollbackMedia();
1486
1487 mData->mFirstSnapshot = pOldFirstSnap; // might have been changed above
1488 mData->mCurrentSnapshot = pOldCurrentSnap; // might have been changed above
1489
1490 /* delete the saved state file (it might have been already created) */
1491 if (mSnapshotData.mSnapshot->stateFilePath().length())
1492 RTFileDelete(mSnapshotData.mSnapshot->stateFilePath().c_str());
1493
1494 mSnapshotData.mSnapshot->uninit();
1495 }
1496
1497 /* clear out the snapshot data */
1498 mSnapshotData.mLastState = MachineState_Null;
1499 mSnapshotData.mSnapshot.setNull();
1500
1501 // save VirtualBox.xml (media registry most probably changed with diff image)
1502 machineLock.release();
1503 AutoWriteLock vboxLock(mParent COMMA_LOCKVAL_SRC_POS);
1504 mParent->saveSettings();
1505
1506 return rc;
1507}
1508
1509////////////////////////////////////////////////////////////////////////////////
1510//
1511// RestoreSnapshot methods (SessionMachine and related tasks)
1512//
1513////////////////////////////////////////////////////////////////////////////////
1514
1515/**
1516 * Implementation for IInternalMachineControl::restoreSnapshot().
1517 *
1518 * Gets called from Console::RestoreSnapshot(), and that's basically the
1519 * only thing Console does. Restoring a snapshot happens entirely on the
1520 * server side since the machine cannot be running.
1521 *
1522 * This creates a new thread that does the work and returns a progress
1523 * object to the client which is then returned to the caller of
1524 * Console::RestoreSnapshot().
1525 *
1526 * Actual work then takes place in RestoreSnapshotTask::handler().
1527 *
1528 * @note Locks this + children objects for writing!
1529 *
1530 * @param aInitiator in: rhe console on which Console::RestoreSnapshot was called.
1531 * @param aSnapshot in: the snapshot to restore.
1532 * @param aMachineState in: client-side machine state.
1533 * @param aProgress out: progress object to monitor restore thread.
1534 * @return
1535 */
1536STDMETHODIMP SessionMachine::RestoreSnapshot(IConsole *aInitiator,
1537 ISnapshot *aSnapshot,
1538 MachineState_T *aMachineState,
1539 IProgress **aProgress)
1540{
1541 LogFlowThisFuncEnter();
1542
1543 AssertReturn(aInitiator, E_INVALIDARG);
1544 AssertReturn(aSnapshot && aMachineState && aProgress, E_POINTER);
1545
1546 AutoCaller autoCaller(this);
1547 AssertComRCReturn(autoCaller.rc(), autoCaller.rc());
1548
1549 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1550
1551 // machine must not be running
1552 ComAssertRet(!Global::IsOnlineOrTransient(mData->mMachineState),
1553 E_FAIL);
1554
1555 ComObjPtr<Snapshot> pSnapshot(static_cast<Snapshot*>(aSnapshot));
1556 ComObjPtr<SnapshotMachine> pSnapMachine = pSnapshot->getSnapshotMachine();
1557
1558 // create a progress object. The number of operations is:
1559 // 1 (preparing) + # of hard disks + 1 (if we need to copy the saved state file) */
1560 LogFlowThisFunc(("Going thru snapshot machine attachments to determine progress setup\n"));
1561
1562 ULONG ulOpCount = 1; // one for preparations
1563 ULONG ulTotalWeight = 1; // one for preparations
1564 for (MediaData::AttachmentList::iterator it = pSnapMachine->mMediaData->mAttachments.begin();
1565 it != pSnapMachine->mMediaData->mAttachments.end();
1566 ++it)
1567 {
1568 ComObjPtr<MediumAttachment> &pAttach = *it;
1569 AutoReadLock attachLock(pAttach COMMA_LOCKVAL_SRC_POS);
1570 if (pAttach->getType() == DeviceType_HardDisk)
1571 {
1572 ++ulOpCount;
1573 ++ulTotalWeight; // assume one MB weight for each differencing hard disk to manage
1574 Assert(pAttach->getMedium());
1575 LogFlowThisFunc(("op %d: considering hard disk attachment %s\n", ulOpCount, pAttach->getMedium()->getName().c_str()));
1576 }
1577 }
1578
1579 ULONG ulStateFileSizeMB = 0;
1580 if (pSnapshot->stateFilePath().length())
1581 {
1582 ++ulOpCount; // one for the saved state
1583
1584 uint64_t ullSize;
1585 int irc = RTFileQuerySize(pSnapshot->stateFilePath().c_str(), &ullSize);
1586 if (!RT_SUCCESS(irc))
1587 // if we can't access the file here, then we'll be doomed later also, so fail right away
1588 setError(E_FAIL, tr("Cannot access state file '%s', runtime error, %Rra"), pSnapshot->stateFilePath().c_str(), irc);
1589 if (ullSize == 0) // avoid division by zero
1590 ullSize = _1M;
1591
1592 ulStateFileSizeMB = (ULONG)(ullSize / _1M);
1593 LogFlowThisFunc(("op %d: saved state file '%s' has %RI64 bytes (%d MB)\n",
1594 ulOpCount, pSnapshot->stateFilePath().raw(), ullSize, ulStateFileSizeMB));
1595
1596 ulTotalWeight += ulStateFileSizeMB;
1597 }
1598
1599 ComObjPtr<Progress> pProgress;
1600 pProgress.createObject();
1601 pProgress->init(mParent, aInitiator,
1602 BstrFmt(tr("Restoring snapshot '%s'"), pSnapshot->getName().c_str()),
1603 FALSE /* aCancelable */,
1604 ulOpCount,
1605 ulTotalWeight,
1606 Bstr(tr("Restoring machine settings")),
1607 1);
1608
1609 /* create and start the task on a separate thread (note that it will not
1610 * start working until we release alock) */
1611 RestoreSnapshotTask *task = new RestoreSnapshotTask(this,
1612 pProgress,
1613 pSnapshot,
1614 ulStateFileSizeMB);
1615 int vrc = RTThreadCreate(NULL,
1616 taskHandler,
1617 (void*)task,
1618 0,
1619 RTTHREADTYPE_MAIN_WORKER,
1620 0,
1621 "RestoreSnap");
1622 if (RT_FAILURE(vrc))
1623 {
1624 delete task;
1625 ComAssertRCRet(vrc, E_FAIL);
1626 }
1627
1628 /* set the proper machine state (note: after creating a Task instance) */
1629 setMachineState(MachineState_RestoringSnapshot);
1630
1631 /* return the progress to the caller */
1632 pProgress.queryInterfaceTo(aProgress);
1633
1634 /* return the new state to the caller */
1635 *aMachineState = mData->mMachineState;
1636
1637 LogFlowThisFuncLeave();
1638
1639 return S_OK;
1640}
1641
1642/**
1643 * Worker method for the restore snapshot thread created by SessionMachine::RestoreSnapshot().
1644 * This method gets called indirectly through SessionMachine::taskHandler() which then
1645 * calls RestoreSnapshotTask::handler().
1646 *
1647 * The RestoreSnapshotTask contains the progress object returned to the console by
1648 * SessionMachine::RestoreSnapshot, through which progress and results are reported.
1649 *
1650 * @note Locks mParent + this object for writing.
1651 *
1652 * @param aTask Task data.
1653 */
1654void SessionMachine::restoreSnapshotHandler(RestoreSnapshotTask &aTask)
1655{
1656 LogFlowThisFuncEnter();
1657
1658 AutoCaller autoCaller(this);
1659
1660 LogFlowThisFunc(("state=%d\n", autoCaller.state()));
1661 if (!autoCaller.isOk())
1662 {
1663 /* we might have been uninitialized because the session was accidentally
1664 * closed by the client, so don't assert */
1665 aTask.pProgress->notifyComplete(E_FAIL,
1666 COM_IIDOF(IMachine),
1667 getComponentName(),
1668 tr("The session has been accidentally closed"));
1669
1670 LogFlowThisFuncLeave();
1671 return;
1672 }
1673
1674 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1675
1676 /* discard all current changes to mUserData (name, OSType etc.) (note that
1677 * the machine is powered off, so there is no need to inform the direct
1678 * session) */
1679 if (mData->flModifications)
1680 rollback(false /* aNotify */);
1681
1682 HRESULT rc = S_OK;
1683
1684 bool stateRestored = false;
1685 bool fNeedsSaveSettings = false;
1686
1687 try
1688 {
1689 /* Delete the saved state file if the machine was Saved prior to this
1690 * operation */
1691 if (aTask.machineStateBackup == MachineState_Saved)
1692 {
1693 Assert(!mSSData->mStateFilePath.isEmpty());
1694 RTFileDelete(mSSData->mStateFilePath.c_str());
1695 mSSData->mStateFilePath.setNull();
1696 aTask.modifyBackedUpState(MachineState_PoweredOff);
1697 rc = saveStateSettings(SaveSTS_StateFilePath);
1698 if (FAILED(rc)) throw rc;
1699 }
1700
1701 RTTIMESPEC snapshotTimeStamp;
1702 RTTimeSpecSetMilli(&snapshotTimeStamp, 0);
1703
1704 {
1705 AutoReadLock snapshotLock(aTask.pSnapshot COMMA_LOCKVAL_SRC_POS);
1706
1707 /* remember the timestamp of the snapshot we're restoring from */
1708 snapshotTimeStamp = aTask.pSnapshot->getTimeStamp();
1709
1710 ComPtr<SnapshotMachine> pSnapshotMachine(aTask.pSnapshot->getSnapshotMachine());
1711
1712 /* copy all hardware data from the snapshot */
1713 copyFrom(pSnapshotMachine);
1714
1715 LogFlowThisFunc(("Restoring hard disks from the snapshot...\n"));
1716
1717 // restore the attachments from the snapshot
1718 setModified(IsModified_Storage);
1719 mMediaData.backup();
1720 mMediaData->mAttachments = pSnapshotMachine->mMediaData->mAttachments;
1721
1722 /* leave the locks before the potentially lengthy operation */
1723 snapshotLock.release();
1724 alock.leave();
1725
1726 rc = createImplicitDiffs(mUserData->mSnapshotFolderFull,
1727 aTask.pProgress,
1728 1,
1729 false /* aOnline */,
1730 &fNeedsSaveSettings);
1731 if (FAILED(rc)) throw rc;
1732
1733 alock.enter();
1734 snapshotLock.acquire();
1735
1736 /* Note: on success, current (old) hard disks will be
1737 * deassociated/deleted on #commit() called from #saveSettings() at
1738 * the end. On failure, newly created implicit diffs will be
1739 * deleted by #rollback() at the end. */
1740
1741 /* should not have a saved state file associated at this point */
1742 Assert(mSSData->mStateFilePath.isEmpty());
1743
1744 if (!aTask.pSnapshot->stateFilePath().isEmpty())
1745 {
1746 Utf8Str snapStateFilePath = aTask.pSnapshot->stateFilePath();
1747
1748 Utf8Str stateFilePath = Utf8StrFmt("%ls%c{%RTuuid}.sav",
1749 mUserData->mSnapshotFolderFull.raw(),
1750 RTPATH_DELIMITER,
1751 mData->mUuid.raw());
1752
1753 LogFlowThisFunc(("Copying saved state file from '%s' to '%s'...\n",
1754 snapStateFilePath.raw(), stateFilePath.raw()));
1755
1756 aTask.pProgress->SetNextOperation(Bstr(tr("Restoring the execution state")),
1757 aTask.m_ulStateFileSizeMB); // weight
1758
1759 /* leave the lock before the potentially lengthy operation */
1760 snapshotLock.release();
1761 alock.leave();
1762
1763 /* copy the state file */
1764 int vrc = RTFileCopyEx(snapStateFilePath.c_str(),
1765 stateFilePath.c_str(),
1766 0,
1767 progressCallback,
1768 static_cast<IProgress*>(aTask.pProgress));
1769
1770 alock.enter();
1771 snapshotLock.acquire();
1772
1773 if (RT_SUCCESS(vrc))
1774 mSSData->mStateFilePath = stateFilePath;
1775 else
1776 throw setError(E_FAIL,
1777 tr("Could not copy the state file '%s' to '%s' (%Rrc)"),
1778 snapStateFilePath.raw(),
1779 stateFilePath.raw(),
1780 vrc);
1781 }
1782
1783 LogFlowThisFunc(("Setting new current snapshot {%RTuuid}\n", aTask.pSnapshot->getId().raw()));
1784 /* make the snapshot we restored from the current snapshot */
1785 mData->mCurrentSnapshot = aTask.pSnapshot;
1786 }
1787
1788 /* grab differencing hard disks from the old attachments that will
1789 * become unused and need to be auto-deleted */
1790 std::list< ComObjPtr<MediumAttachment> > llDiffAttachmentsToDelete;
1791
1792 for (MediaData::AttachmentList::const_iterator it = mMediaData.backedUpData()->mAttachments.begin();
1793 it != mMediaData.backedUpData()->mAttachments.end();
1794 ++it)
1795 {
1796 ComObjPtr<MediumAttachment> pAttach = *it;
1797 ComObjPtr<Medium> pMedium = pAttach->getMedium();
1798
1799 /* while the hard disk is attached, the number of children or the
1800 * parent cannot change, so no lock */
1801 if ( !pMedium.isNull()
1802 && pAttach->getType() == DeviceType_HardDisk
1803 && !pMedium->getParent().isNull()
1804 && pMedium->getChildren().size() == 0
1805 )
1806 {
1807 LogFlowThisFunc(("Picked differencing image '%s' for deletion\n", pMedium->getName().raw()));
1808
1809 llDiffAttachmentsToDelete.push_back(pAttach);
1810 }
1811 }
1812
1813 int saveFlags = 0;
1814
1815 /* we have already deleted the current state, so set the execution
1816 * state accordingly no matter of the delete snapshot result */
1817 if (!mSSData->mStateFilePath.isEmpty())
1818 setMachineState(MachineState_Saved);
1819 else
1820 setMachineState(MachineState_PoweredOff);
1821
1822 updateMachineStateOnClient();
1823 stateRestored = true;
1824
1825 /* assign the timestamp from the snapshot */
1826 Assert(RTTimeSpecGetMilli (&snapshotTimeStamp) != 0);
1827 mData->mLastStateChange = snapshotTimeStamp;
1828
1829 // detach the current-state diffs that we detected above and build a list of
1830 // image files to delete _after_ saveSettings()
1831
1832 MediaList llDiffsToDelete;
1833
1834 for (std::list< ComObjPtr<MediumAttachment> >::iterator it = llDiffAttachmentsToDelete.begin();
1835 it != llDiffAttachmentsToDelete.end();
1836 ++it)
1837 {
1838 ComObjPtr<MediumAttachment> pAttach = *it; // guaranteed to have only attachments where medium != NULL
1839 ComObjPtr<Medium> pMedium = pAttach->getMedium();
1840
1841 AutoWriteLock mlock(pMedium COMMA_LOCKVAL_SRC_POS);
1842
1843 LogFlowThisFunc(("Detaching old current state in differencing image '%s'\n", pMedium->getName().raw()));
1844
1845 // Normally we "detach" the medium by removing the attachment object
1846 // from the current machine data; saveSettings() below would then
1847 // compare the current machine data with the one in the backup
1848 // and actually call Medium::detachFrom(). But that works only half
1849 // the time in our case so instead we force a detachment here:
1850 // remove from machine data
1851 mMediaData->mAttachments.remove(pAttach);
1852 // remove it from the backup or else saveSettings will try to detach
1853 // it again and assert
1854 mMediaData.backedUpData()->mAttachments.remove(pAttach);
1855 // then clean up backrefs
1856 pMedium->detachFrom(mData->mUuid);
1857
1858 llDiffsToDelete.push_back(pMedium);
1859 }
1860
1861 // save machine settings, reset the modified flag and commit;
1862 rc = saveSettings(&fNeedsSaveSettings, SaveS_ResetCurStateModified | saveFlags);
1863 if (FAILED(rc)) throw rc;
1864
1865 // let go of the locks while we're deleting image files below
1866 alock.leave();
1867 // from here on we cannot roll back on failure any more
1868
1869 for (MediaList::iterator it = llDiffsToDelete.begin();
1870 it != llDiffsToDelete.end();
1871 ++it)
1872 {
1873 ComObjPtr<Medium> &pMedium = *it;
1874 LogFlowThisFunc(("Deleting old current state in differencing image '%s'\n", pMedium->getName().raw()));
1875
1876 HRESULT rc2 = pMedium->deleteStorage(NULL /* aProgress */,
1877 true /* aWait */,
1878 &fNeedsSaveSettings);
1879 // ignore errors here because we cannot roll back after saveSettings() above
1880 if (SUCCEEDED(rc2))
1881 pMedium->uninit();
1882 }
1883
1884 if (fNeedsSaveSettings)
1885 {
1886 // finally, VirtualBox.xml needs saving too
1887 AutoWriteLock vboxLock(mParent COMMA_LOCKVAL_SRC_POS);
1888 mParent->saveSettings();
1889 }
1890 }
1891 catch (HRESULT aRC)
1892 {
1893 rc = aRC;
1894 }
1895
1896 if (FAILED(rc))
1897 {
1898 /* preserve existing error info */
1899 ErrorInfoKeeper eik;
1900
1901 /* undo all changes on failure */
1902 rollback(false /* aNotify */);
1903
1904 if (!stateRestored)
1905 {
1906 /* restore the machine state */
1907 setMachineState(aTask.machineStateBackup);
1908 updateMachineStateOnClient();
1909 }
1910 }
1911
1912 /* set the result (this will try to fetch current error info on failure) */
1913 aTask.pProgress->notifyComplete(rc);
1914
1915 if (SUCCEEDED(rc))
1916 mParent->onSnapshotDeleted(mData->mUuid, Guid());
1917
1918 LogFlowThisFunc(("Done restoring snapshot (rc=%08X)\n", rc));
1919
1920 LogFlowThisFuncLeave();
1921}
1922
1923////////////////////////////////////////////////////////////////////////////////
1924//
1925// DeleteSnapshot methods (SessionMachine and related tasks)
1926//
1927////////////////////////////////////////////////////////////////////////////////
1928
1929/**
1930 * Implementation for IInternalMachineControl::deleteSnapshot().
1931 *
1932 * Gets called from Console::DeleteSnapshot(), and that's basically the
1933 * only thing Console does. Deleting a snapshot happens entirely on the
1934 * server side since the machine cannot be running.
1935 *
1936 * This creates a new thread that does the work and returns a progress
1937 * object to the client which is then returned to the caller of
1938 * Console::DeleteSnapshot().
1939 *
1940 * Actual work then takes place in DeleteSnapshotTask::handler().
1941 *
1942 * @note Locks mParent + this + children objects for writing!
1943 */
1944STDMETHODIMP SessionMachine::DeleteSnapshot(IConsole *aInitiator,
1945 IN_BSTR aId,
1946 MachineState_T *aMachineState,
1947 IProgress **aProgress)
1948{
1949 LogFlowThisFuncEnter();
1950
1951 Guid id(aId);
1952 AssertReturn(aInitiator && !id.isEmpty(), E_INVALIDARG);
1953 AssertReturn(aMachineState && aProgress, E_POINTER);
1954
1955 AutoCaller autoCaller(this);
1956 AssertComRCReturn(autoCaller.rc(), autoCaller.rc());
1957
1958 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1959
1960 // machine must not be running
1961 ComAssertRet(!Global::IsOnlineOrTransient(mData->mMachineState), E_FAIL);
1962
1963 ComObjPtr<Snapshot> pSnapshot;
1964 HRESULT rc = findSnapshot(id, pSnapshot, true /* aSetError */);
1965 if (FAILED(rc)) return rc;
1966
1967 AutoWriteLock snapshotLock(pSnapshot COMMA_LOCKVAL_SRC_POS);
1968
1969 size_t childrenCount = pSnapshot->getChildrenCount();
1970 if (childrenCount > 1)
1971 return setError(VBOX_E_INVALID_OBJECT_STATE,
1972 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"),
1973 pSnapshot->getName().c_str(),
1974 mUserData->mName.raw(),
1975 childrenCount);
1976
1977 /* If the snapshot being deleted is the current one, ensure current
1978 * settings are committed and saved.
1979 */
1980 if (pSnapshot == mData->mCurrentSnapshot)
1981 {
1982 if (mData->flModifications)
1983 {
1984 rc = saveSettings(NULL);
1985 // no need to change for whether VirtualBox.xml needs saving since
1986 // we can't have a machine XML rename pending at this point
1987 if (FAILED(rc)) return rc;
1988 }
1989 }
1990
1991 ComObjPtr<SnapshotMachine> pSnapMachine = pSnapshot->getSnapshotMachine();
1992
1993 /* create a progress object. The number of operations is:
1994 * 1 (preparing) + 1 if the snapshot is online + # of normal hard disks
1995 */
1996 LogFlowThisFunc(("Going thru snapshot machine attachments to determine progress setup\n"));
1997
1998 ULONG ulOpCount = 1; // one for preparations
1999 ULONG ulTotalWeight = 1; // one for preparations
2000
2001 if (pSnapshot->stateFilePath().length())
2002 {
2003 ++ulOpCount;
2004 ++ulTotalWeight; // assume 1 MB for deleting the state file
2005 }
2006
2007 // count normal hard disks and add their sizes to the weight
2008 for (MediaData::AttachmentList::iterator it = pSnapMachine->mMediaData->mAttachments.begin();
2009 it != pSnapMachine->mMediaData->mAttachments.end();
2010 ++it)
2011 {
2012 ComObjPtr<MediumAttachment> &pAttach = *it;
2013 AutoReadLock attachLock(pAttach COMMA_LOCKVAL_SRC_POS);
2014 if (pAttach->getType() == DeviceType_HardDisk)
2015 {
2016 ComObjPtr<Medium> pHD = pAttach->getMedium();
2017 Assert(pHD);
2018 AutoReadLock mlock(pHD COMMA_LOCKVAL_SRC_POS);
2019
2020 MediumType_T type = pHD->getType();
2021 if (type != MediumType_Writethrough) // writethrough images are unaffected by snapshots, so do nothing for them
2022 {
2023 // normal or immutable media need attention
2024 ++ulOpCount;
2025 ulTotalWeight += (ULONG)(pHD->getSize() / _1M);
2026 }
2027 LogFlowThisFunc(("op %d: considering hard disk attachment %s\n", ulOpCount, pHD->getName().c_str()));
2028 }
2029 }
2030
2031 ComObjPtr<Progress> pProgress;
2032 pProgress.createObject();
2033 pProgress->init(mParent, aInitiator,
2034 BstrFmt(tr("Deleting snapshot '%s'"), pSnapshot->getName().c_str()),
2035 FALSE /* aCancelable */,
2036 ulOpCount,
2037 ulTotalWeight,
2038 Bstr(tr("Setting up")),
2039 1);
2040
2041 /* create and start the task on a separate thread */
2042 DeleteSnapshotTask *task = new DeleteSnapshotTask(this, pProgress, pSnapshot);
2043 int vrc = RTThreadCreate(NULL,
2044 taskHandler,
2045 (void*)task,
2046 0,
2047 RTTHREADTYPE_MAIN_WORKER,
2048 0,
2049 "DeleteSnapshot");
2050 if (RT_FAILURE(vrc))
2051 {
2052 delete task;
2053 return E_FAIL;
2054 }
2055
2056 // the task might start running but will block on acquiring the machine's write lock
2057 // which we acquired above; once this function leaves, the task will be unblocked;
2058 // set the proper machine state here now (note: after creating a Task instance)
2059 setMachineState(MachineState_DeletingSnapshot);
2060
2061 /* return the progress to the caller */
2062 pProgress.queryInterfaceTo(aProgress);
2063
2064 /* return the new state to the caller */
2065 *aMachineState = mData->mMachineState;
2066
2067 LogFlowThisFuncLeave();
2068
2069 return S_OK;
2070}
2071
2072/**
2073 * Helper struct for SessionMachine::deleteSnapshotHandler().
2074 */
2075struct MediumDeleteRec
2076{
2077 MediumDeleteRec()
2078 : mpMediumLockList(NULL)
2079 {}
2080
2081 MediumDeleteRec(const ComObjPtr<Medium> &aHd,
2082 const ComObjPtr<Medium> &aSource,
2083 const ComObjPtr<Medium> &aTarget,
2084 bool fMergeForward,
2085 const ComObjPtr<Medium> &aParentForTarget,
2086 const MediaList &aChildrenToReparent,
2087 MediumLockList *aMediumLockList)
2088 : mpHD(aHd),
2089 mpSource(aSource),
2090 mpTarget(aTarget),
2091 mfMergeForward(fMergeForward),
2092 mpParentForTarget(aParentForTarget),
2093 mChildrenToReparent(aChildrenToReparent),
2094 mpMediumLockList(aMediumLockList)
2095 {}
2096
2097 MediumDeleteRec(const ComObjPtr<Medium> &aHd,
2098 const ComObjPtr<Medium> &aSource,
2099 const ComObjPtr<Medium> &aTarget,
2100 bool fMergeForward,
2101 const ComObjPtr<Medium> &aParentForTarget,
2102 const MediaList &aChildrenToReparent,
2103 MediumLockList *aMediumLockList,
2104 const ComObjPtr<MediumAttachment> &aReplaceHda,
2105 const Guid &aSnapshotId)
2106 : mpHD(aHd),
2107 mpSource(aSource),
2108 mpTarget(aTarget),
2109 mfMergeForward(fMergeForward),
2110 mpParentForTarget(aParentForTarget),
2111 mChildrenToReparent(aChildrenToReparent),
2112 mpMediumLockList(aMediumLockList),
2113 mpReplaceHda(aReplaceHda),
2114 mSnapshotId(aSnapshotId)
2115 {}
2116
2117 ComObjPtr<Medium> mpHD;
2118 ComObjPtr<Medium> mpSource;
2119 ComObjPtr<Medium> mpTarget;
2120 bool mfMergeForward;
2121 ComObjPtr<Medium> mpParentForTarget;
2122 MediaList mChildrenToReparent;
2123 MediumLockList *mpMediumLockList;
2124 /* these are for the replace hard disk case: */
2125 ComObjPtr<MediumAttachment> mpReplaceHda;
2126 Guid mSnapshotId;
2127};
2128
2129typedef std::list<MediumDeleteRec> MediumDeleteRecList;
2130
2131/**
2132 * Worker method for the delete snapshot thread created by SessionMachine::DeleteSnapshot().
2133 * This method gets called indirectly through SessionMachine::taskHandler() which then
2134 * calls DeleteSnapshotTask::handler().
2135 *
2136 * The DeleteSnapshotTask contains the progress object returned to the console by
2137 * SessionMachine::DeleteSnapshot, through which progress and results are reported.
2138 *
2139 * SessionMachine::DeleteSnapshot() has set the machne state to MachineState_DeletingSnapshot
2140 * right after creating this task. Since we block on the machine write lock at the beginning,
2141 * once that has been acquired, we can assume that the machine state is indeed that.
2142 *
2143 * @note Locks the machine + the snapshot + the media tree for writing!
2144 *
2145 * @param aTask Task data.
2146 */
2147void SessionMachine::deleteSnapshotHandler(DeleteSnapshotTask &aTask)
2148{
2149 LogFlowThisFuncEnter();
2150
2151 AutoCaller autoCaller(this);
2152
2153 LogFlowThisFunc(("state=%d\n", autoCaller.state()));
2154 if (!autoCaller.isOk())
2155 {
2156 /* we might have been uninitialized because the session was accidentally
2157 * closed by the client, so don't assert */
2158 aTask.pProgress->notifyComplete(E_FAIL,
2159 COM_IIDOF(IMachine),
2160 getComponentName(),
2161 tr("The session has been accidentally closed"));
2162 LogFlowThisFuncLeave();
2163 return;
2164 }
2165
2166 MediumDeleteRecList toDelete;
2167
2168 HRESULT rc = S_OK;
2169
2170 bool fMachineSettingsChanged = false; // Machine
2171 bool fNeedsSaveSettings = false; // VirtualBox.xml
2172
2173 Guid snapshotId;
2174
2175 try
2176 {
2177 /* Locking order: */
2178 AutoMultiWriteLock3 multiLock(this->lockHandle(), // machine
2179 aTask.pSnapshot->lockHandle(), // snapshot
2180 &mParent->getMediaTreeLockHandle() // media tree
2181 COMMA_LOCKVAL_SRC_POS);
2182 // once we have this lock, we know that SessionMachine::DeleteSnapshot()
2183 // has exited after setting the machine state to MachineState_DeletingSnapshot
2184
2185 ComObjPtr<SnapshotMachine> pSnapMachine = aTask.pSnapshot->getSnapshotMachine();
2186 // no need to lock the snapshot machine since it is const by definiton
2187 Guid machineId = pSnapMachine->getId();
2188
2189 // save the snapshot ID (for callbacks)
2190 snapshotId = aTask.pSnapshot->getId();
2191
2192 // first pass:
2193 LogFlowThisFunc(("1: Checking hard disk merge prerequisites...\n"));
2194
2195 // go thru the attachments of the snapshot machine
2196 // (the media in here point to the disk states _before_ the snapshot
2197 // was taken, i.e. the state we're restoring to; for each such
2198 // medium, we will need to merge it with its one and only child (the
2199 // diff image holding the changes written after the snapshot was taken)
2200 for (MediaData::AttachmentList::iterator it = pSnapMachine->mMediaData->mAttachments.begin();
2201 it != pSnapMachine->mMediaData->mAttachments.end();
2202 ++it)
2203 {
2204 ComObjPtr<MediumAttachment> &pAttach = *it;
2205 AutoReadLock attachLock(pAttach COMMA_LOCKVAL_SRC_POS);
2206 if (pAttach->getType() != DeviceType_HardDisk)
2207 continue;
2208
2209 ComObjPtr<Medium> pHD = pAttach->getMedium();
2210 Assert(!pHD.isNull());
2211
2212 {
2213 // writethrough images are unaffected by snapshots, skip them
2214 AutoReadLock medlock(pHD COMMA_LOCKVAL_SRC_POS);
2215 MediumType_T type = pHD->getType();
2216 if (type == MediumType_Writethrough)
2217 continue;
2218 }
2219
2220#ifdef DEBUG
2221 pHD->dumpBackRefs();
2222#endif
2223
2224 // needs to be merged with child or deleted, check prerequisites
2225 ComObjPtr<Medium> pTarget;
2226 ComObjPtr<Medium> pSource;
2227 bool fMergeForward = false;
2228 ComObjPtr<Medium> pParentForTarget;
2229 MediaList childrenToReparent;
2230 MediumLockList *pMediumLockList = NULL;
2231 rc = prepareDeleteSnapshotMedium(pHD, machineId, snapshotId,
2232 pSource, pTarget, fMergeForward,
2233 pParentForTarget,
2234 childrenToReparent,
2235 pMediumLockList);
2236 if (FAILED(rc)) throw rc;
2237
2238 // for simplicity, we merge pHd onto its child (forward merge), not
2239 // the other way round, because that saves us from updating the
2240 // attachments for the machine that follows the snapshot (next
2241 // snapshot or real machine), unless it's a base image:
2242
2243 if (pMediumLockList != NULL && !fMergeForward)
2244 {
2245 // parent is null -> this disk is a base hard disk: we will
2246 // then do a backward merge, i.e. merge its only child onto the
2247 // base disk. Here we need then to update the attachment that
2248 // refers to the child and have it point to the parent instead
2249 Assert(pHD->getParent().isNull());
2250 Assert(pHD->getChildren().size() == 1);
2251
2252 ComObjPtr<Medium> pReplaceHD = pHD->getChildren().front();
2253
2254 ComAssertThrow(pReplaceHD == pSource, E_FAIL);
2255
2256 const Guid *pReplaceMachineId = pSource->getFirstMachineBackrefId();
2257 NOREF(pReplaceMachineId);
2258 Assert(pReplaceMachineId);
2259 Assert(*pReplaceMachineId == mData->mUuid);
2260
2261 Guid snapshotId2;
2262 const Guid *pSnapshotId = pSource->getFirstMachineBackrefSnapshotId();
2263 if (pSnapshotId)
2264 snapshotId2 = *pSnapshotId;
2265
2266 HRESULT rc2 = S_OK;
2267
2268 attachLock.release();
2269
2270 // First we must detach the child otherwise mergeTo() will
2271 // will assert because it is going to delete the child.
2272 // Adjust the backreferences:
2273 // 1) detach the first child hard disk
2274 rc2 = pSource->detachFrom(mData->mUuid, snapshotId2);
2275 AssertComRC(rc2);
2276 // 2) attach to machine and snapshot
2277 rc2 = pHD->attachTo(mData->mUuid, snapshotId2);
2278 AssertComRC(rc2);
2279
2280 /* replace the hard disk in the attachment object */
2281 if (snapshotId2.isEmpty())
2282 {
2283 /* in current state */
2284 AssertBreak(pAttach = findAttachment(mMediaData->mAttachments, pSource));
2285 }
2286 else
2287 {
2288 /* in snapshot */
2289 ComObjPtr<Snapshot> snapshot;
2290 rc2 = findSnapshot(snapshotId2, snapshot);
2291 AssertComRC(rc2);
2292
2293 /* don't lock the snapshot; cannot be modified outside */
2294 MediaData::AttachmentList &snapAtts = snapshot->getSnapshotMachine()->mMediaData->mAttachments;
2295 AssertBreak(pAttach = findAttachment(snapAtts, pSource));
2296 }
2297
2298 AutoWriteLock attLock(pAttach COMMA_LOCKVAL_SRC_POS);
2299 pAttach->updateMedium(pHD, false /* aImplicit */);
2300
2301 toDelete.push_back(MediumDeleteRec(pHD, pSource, pTarget,
2302 fMergeForward,
2303 pParentForTarget,
2304 childrenToReparent,
2305 pMediumLockList,
2306 pAttach, snapshotId2));
2307 }
2308 else
2309 toDelete.push_back(MediumDeleteRec(pHD, pSource, pTarget,
2310 fMergeForward,
2311 pParentForTarget,
2312 childrenToReparent,
2313 pMediumLockList));
2314 }
2315
2316 // we can release the lock now since the machine state is MachineState_DeletingSnapshot
2317 multiLock.release();
2318
2319 /** @todo r=klaus the comment below makes only limited sense to me.
2320 * Deleting the snapshot early has some benefits, but also the drawback
2321 * that a normal user surely has difficulties to perform the not
2322 * completed merge operations later. Before IMedium::mergeTo is added
2323 * to VBoxManage it actually is completely impossible. */
2324
2325 /* Now we checked that we can successfully merge all normal hard disks
2326 * (unless a runtime error like end-of-disc happens). Prior to
2327 * performing the actual merge, we want to delete the snapshot itself
2328 * and remove it from the XML file to make sure that a possible merge
2329 * runtime error will not make this snapshot inconsistent because of
2330 * the partially merged or corrupted hard disks */
2331
2332 /* second pass: */
2333 LogFlowThisFunc(("2: Deleting snapshot...\n"));
2334
2335 {
2336 // saveAllSnapshots() needs a machine lock, and the snapshots
2337 // tree is protected by the machine lock as well
2338 AutoWriteLock machineLock(this COMMA_LOCKVAL_SRC_POS);
2339
2340 ComObjPtr<Snapshot> parentSnapshot = aTask.pSnapshot->getParent();
2341 Utf8Str stateFilePath = aTask.pSnapshot->stateFilePath();
2342
2343 // Note that deleting the snapshot will deassociate it from the
2344 // hard disks which will allow the merge+delete operation for them
2345 aTask.pSnapshot->beginSnapshotDelete();
2346 aTask.pSnapshot->uninit();
2347 // this requests the machine lock in turn when deleting all the children
2348 // in the snapshot machine
2349
2350 rc = saveAllSnapshots(*mData->pMachineConfigFile);
2351 machineLock.release();
2352 if (FAILED(rc)) throw rc;
2353
2354 if (!stateFilePath.isEmpty())
2355 {
2356 aTask.pProgress->SetNextOperation(Bstr(tr("Deleting the execution state")),
2357 1); // weight
2358
2359 RTFileDelete(stateFilePath.c_str());
2360 }
2361
2362 /// @todo NEWMEDIA to provide a good level of fauilt tolerance, we
2363 /// should restore the shapshot in the snapshot tree if
2364 /// saveSnapshotSettings fails. Actually, we may call
2365 /// #saveSnapshotSettings() with a special flag that will tell it to
2366 /// skip the given snapshot as if it would have been deleted and
2367 /// only actually delete it if the save operation succeeds.
2368 }
2369
2370 /* here we come when we've irreversibly eleted the snapshot which
2371 * means that the VM settigns (our relevant changes to mData) need to be
2372 * saved too */
2373 /// @todo NEWMEDIA maybe save everything in one operation in place of
2374 /// saveSnapshotSettings() above
2375 fMachineSettingsChanged = true;
2376
2377 /* third pass: */
2378 LogFlowThisFunc(("3: Performing actual hard disk merging...\n"));
2379
2380 /// @todo NEWMEDIA turn the following errors into warnings because the
2381 /// snapshot itself has been already deleted (and interpret these
2382 /// warnings properly on the GUI side)
2383 for (MediumDeleteRecList::iterator it = toDelete.begin();
2384 it != toDelete.end();)
2385 {
2386 const ComObjPtr<Medium> &pMedium(it->mpHD);
2387 ULONG ulWeight;
2388
2389 {
2390 AutoReadLock alock(pMedium COMMA_LOCKVAL_SRC_POS);
2391 ulWeight = (ULONG)(pMedium->getSize() / _1M);
2392 }
2393
2394 aTask.pProgress->SetNextOperation(BstrFmt(tr("Merging differencing image '%s'"),
2395 pMedium->getName().raw()),
2396 ulWeight);
2397
2398 if (it->mpMediumLockList == NULL)
2399 {
2400 /* no real merge needed, just updating state and delete
2401 * diff files if necessary */
2402 AutoMultiWriteLock2 mLock(&mParent->getMediaTreeLockHandle(), pMedium->lockHandle() COMMA_LOCKVAL_SRC_POS);
2403
2404 Assert( !it->mfMergeForward
2405 || pMedium->getChildren().size() == 0);
2406
2407 /* Delete the differencing hard disk (has no children). Two
2408 * exceptions: if it's the last medium in the chain or if it's
2409 * a backward merge we don't want to handle due to complextity.
2410 * In both cases leave the image in place. If it's the first
2411 * exception the user can delete it later if he wants. */
2412 if (!pMedium->getParent().isNull())
2413 {
2414 Assert(pMedium->getState() == MediumState_Deleting);
2415 rc = pMedium->deleteStorage(&aTask.pProgress,
2416 true /* aWait */,
2417 &fNeedsSaveSettings);
2418 if (SUCCEEDED(rc))
2419 pMedium->uninit();
2420 }
2421 }
2422 else
2423 {
2424 /* Normal merge operation, in the direction decided earlier. */
2425 rc = it->mpSource->mergeTo(it->mpTarget, it->mfMergeForward,
2426 it->mpParentForTarget,
2427 it->mChildrenToReparent,
2428 it->mpMediumLockList,
2429 &aTask.pProgress, true /* aWait */,
2430 &fNeedsSaveSettings);
2431 if (SUCCEEDED(rc))
2432 {
2433 /* On success delete the no longer needed medium lock
2434 * list. This unlocks the media as well. */
2435 delete it->mpMediumLockList;
2436 it->mpMediumLockList = NULL;
2437 it->mpSource->uninit();
2438 }
2439 }
2440
2441 if (FAILED(rc)) throw rc;
2442
2443 /* prevent from calling cancelDeleteSnapshotMedium() */
2444 it = toDelete.erase(it);
2445 }
2446 }
2447 catch (HRESULT aRC) { rc = aRC; }
2448
2449 if (FAILED(rc))
2450 {
2451 AutoMultiWriteLock2 multiLock(this->lockHandle(), // machine
2452 &mParent->getMediaTreeLockHandle() // media tree
2453 COMMA_LOCKVAL_SRC_POS);
2454
2455 // un-prepare the remaining hard disks
2456 for (MediumDeleteRecList::const_iterator it = toDelete.begin();
2457 it != toDelete.end();
2458 ++it)
2459 {
2460 cancelDeleteSnapshotMedium(it->mpHD, it->mpSource, it->mpTarget,
2461 it->mChildrenToReparent,
2462 it->mpMediumLockList,
2463 it->mpReplaceHda, it->mSnapshotId);
2464 }
2465 }
2466
2467 // whether we were successful or not, we need to set the machine
2468 // state and save the machine settings;
2469 {
2470 // preserve existing error info so that the result can
2471 // be properly reported to the progress object below
2472 ErrorInfoKeeper eik;
2473
2474 // restore the machine state that was saved when the
2475 // task was started
2476 setMachineState(aTask.machineStateBackup);
2477 updateMachineStateOnClient();
2478
2479 if (fMachineSettingsChanged || fNeedsSaveSettings)
2480 {
2481 if (fMachineSettingsChanged)
2482 {
2483 AutoWriteLock machineLock(this COMMA_LOCKVAL_SRC_POS);
2484 saveSettings(&fNeedsSaveSettings, SaveS_InformCallbacksAnyway);
2485 }
2486
2487 if (fNeedsSaveSettings)
2488 {
2489 AutoWriteLock vboxLock(mParent COMMA_LOCKVAL_SRC_POS);
2490 mParent->saveSettings();
2491 }
2492 }
2493 }
2494
2495 // report the result (this will try to fetch current error info on failure)
2496 aTask.pProgress->notifyComplete(rc);
2497
2498 if (SUCCEEDED(rc))
2499 mParent->onSnapshotDeleted(mData->mUuid, snapshotId);
2500
2501 LogFlowThisFunc(("Done deleting snapshot (rc=%08X)\n", rc));
2502 LogFlowThisFuncLeave();
2503}
2504
2505/**
2506 * Checks that this hard disk (part of a snapshot) may be deleted/merged and
2507 * performs necessary state changes. Must not be called for writethrough disks
2508 * because there is nothing to delete/merge then.
2509 *
2510 * This method is to be called prior to calling #deleteSnapshotMedium().
2511 * If #deleteSnapshotMedium() is not called or fails, the state modifications
2512 * performed by this method must be undone by #cancelDeleteSnapshotMedium().
2513 *
2514 * @param aHD Hard disk which is connected to the snapshot.
2515 * @param aMachineId UUID of machine this hard disk is attached to.
2516 * @param aSnapshotId UUID of snapshot this hard disk is attached to. May
2517 * be a zero UUID if no snapshot is applicable.
2518 * @param aSource Source hard disk for merge (out).
2519 * @param aTarget Target hard disk for merge (out).
2520 * @param aMergeForward Merge direction decision (out).
2521 * @param aParentForTarget New parent if target needs to be reparented (out).
2522 * @param aChildrenToReparent Children which have to be reparented to the
2523 * target (out).
2524 * @param aMediumLockList Where to store the created medium lock list (may
2525 * return NULL if no real merge is necessary).
2526 *
2527 * @note Caller must hold media tree lock for writing. This locks this object
2528 * and every medium object on the merge chain for writing.
2529 */
2530HRESULT SessionMachine::prepareDeleteSnapshotMedium(const ComObjPtr<Medium> &aHD,
2531 const Guid &aMachineId,
2532 const Guid &aSnapshotId,
2533 ComObjPtr<Medium> &aSource,
2534 ComObjPtr<Medium> &aTarget,
2535 bool &aMergeForward,
2536 ComObjPtr<Medium> &aParentForTarget,
2537 MediaList &aChildrenToReparent,
2538 MediumLockList * &aMediumLockList)
2539{
2540 Assert(mParent->getMediaTreeLockHandle().isWriteLockOnCurrentThread());
2541
2542 AutoWriteLock alock(aHD COMMA_LOCKVAL_SRC_POS);
2543
2544 // Medium must not be writethrough at this point
2545 AssertReturn(aHD->getType() != MediumType_Writethrough, E_FAIL);
2546
2547 aMediumLockList = NULL;
2548
2549 if (aHD->getChildren().size() == 0)
2550 {
2551 /* special treatment of the last hard disk in the chain: */
2552 if (aHD->getParent().isNull())
2553 {
2554 /* lock only, to prevent any usage until the snapshot deletion
2555 * is completed */
2556 return aHD->LockWrite(NULL);
2557 }
2558
2559 /* the differencing hard disk w/o children will be deleted, protect it
2560 * from attaching to other VMs (this is why Deleting) */
2561 return aHD->markForDeletion();
2562 }
2563
2564 /* not going multi-merge as it's too expensive */
2565 if (aHD->getChildren().size() > 1)
2566 return setError(E_FAIL,
2567 tr ("Hard disk '%s' has more than one child hard disk (%d)"),
2568 aHD->getLocationFull().raw(),
2569 aHD->getChildren().size());
2570
2571 ComObjPtr<Medium> pChild = aHD->getChildren().front();
2572
2573 /* we keep this locked, so lock the affected child to make sure the lock
2574 * order is correct when calling prepareMergeTo() */
2575 AutoWriteLock childLock(pChild COMMA_LOCKVAL_SRC_POS);
2576
2577 /* the rest is a normal merge setup */
2578 if (aHD->getParent().isNull())
2579 {
2580 /* base hard disk, backward merge */
2581 const Guid *pMachineId1 = pChild->getFirstMachineBackrefId();
2582 const Guid *pMachineId2 = aHD->getFirstMachineBackrefId();
2583 if (pMachineId1 && pMachineId2 && *pMachineId1 != *pMachineId2)
2584 {
2585 /* backward merge is too tricky, we'll just detach on snapshot
2586 * deletion, so lock only, to prevent any usage */
2587 return aHD->LockWrite(NULL);
2588 }
2589
2590 aSource = pChild;
2591 aTarget = aHD;
2592 }
2593 else
2594 {
2595 /* forward merge */
2596 aSource = aHD;
2597 aTarget = pChild;
2598 }
2599
2600 return aSource->prepareMergeTo(aTarget, &aMachineId, &aSnapshotId,
2601 aMergeForward, aParentForTarget,
2602 aChildrenToReparent, aMediumLockList);
2603}
2604
2605/**
2606 * Cancels the deletion/merging of this hard disk (part of a snapshot). Undoes
2607 * what #prepareDeleteSnapshotMedium() did. Must be called if
2608 * #deleteSnapshotMedium() is not called or fails.
2609 *
2610 * @param aHD Hard disk which is connected to the snapshot.
2611 * @param aSource Source hard disk for merge.
2612 * @param aTarget Source hard disk for merge.
2613 * @param aChildrenToReparent Children to unlock.
2614 * @param aMediumLockList Medium locks to cancel.
2615 * @param aReplaceHda Hard disk attachment to restore.
2616 * @param aSnapshotId Snapshot id to attach the medium to.
2617 *
2618 * @note Locks the medium tree and the hard disks in the chain for writing.
2619 */
2620void SessionMachine::cancelDeleteSnapshotMedium(const ComObjPtr<Medium> &aHD,
2621 const ComObjPtr<Medium> &aSource,
2622 const ComObjPtr<Medium> &aTarget,
2623 const MediaList &aChildrenToReparent,
2624 MediumLockList *aMediumLockList,
2625 const ComObjPtr<MediumAttachment> &aReplaceHda,
2626 const Guid &aSnapshotId)
2627{
2628 if (aMediumLockList == NULL)
2629 {
2630 AutoMultiWriteLock2 mLock(&mParent->getMediaTreeLockHandle(), aHD->lockHandle() COMMA_LOCKVAL_SRC_POS);
2631
2632 Assert(aHD->getChildren().size() == 0);
2633
2634 if (aHD->getParent().isNull())
2635 {
2636 HRESULT rc = aHD->UnlockWrite(NULL);;
2637 AssertComRC(rc);
2638 }
2639 else
2640 {
2641 HRESULT rc = aHD->unmarkForDeletion();
2642 AssertComRC(rc);
2643 }
2644 }
2645 else
2646 aSource->cancelMergeTo(aChildrenToReparent, aMediumLockList);
2647
2648 if (!aReplaceHda.isNull())
2649 {
2650 HRESULT rc = aTarget->attachTo(mData->mUuid, aSnapshotId);
2651 AssertComRC(rc);
2652
2653 rc = aHD->detachFrom(mData->mUuid, aSnapshotId);
2654 AssertComRC(rc);
2655
2656 AutoWriteLock attLock(aReplaceHda COMMA_LOCKVAL_SRC_POS);
2657 aReplaceHda->updateMedium(aTarget, false /* aImplicit */);
2658 }
2659}
Note: See TracBrowser for help on using the repository browser.

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