VirtualBox

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

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

Main: Bandwidth groups for disks (and later network)

This introduces two new interfaces. The first one named IBandwidthGroup
represents one I/O limit and can be assigned to several mediums which
share this limit (which works only for harddisk images with the disabled
host cache).
The second one IBandwdithControl manages the groups and can create new ones
and destroy them if not required anymore.

VBoxManage: commands to access the bandwidth groups

Syntax:
VBoxManage storageattach <uuid|vmname>

...
--bandwidthgroup <name>

--bandwidthgroup assigns the specified device to the given group.

VBoxManage bandwidthctl <uuid|vmname>

--name <name>
--add disk|network
--limit <megabytes per second>
--delete

The --name parameter gives the name of the bandwidth group.
--add creates a new group of the given type (only disk is implemented so far)

with the given name.

--limit sets the limit to the given amount of MB/s

Note that limit can be changed while the VM is running. The VM
will immediately pick up the new limit for the given group name.

--delete deletes the group with the given name if it isn't used anymore.

Trying to delete a still used group will result in an error.

Example:

VBoxManage bandwidthctl "Test VM" --name Limit --add disk --limit 20
Creates a group named Test having a 20 MB/s limit.

VBoxManage storageattach "Test VM" --storagectl "SATA Controller" --port 0 --device 0 --type hdd --medium test.vdi --bandwidthgroup Limit
Adds a new disk to the SATA controller and assigns the bandwidth group Limit to it.

VBoxManage storageattach "Test VM" --storagectl "SATA Controller" --port 0 --device 0 --type hdd --medium test.vdi --bandwidthgroup none
Removes the bandwidth limit from the disk.

VBoxManage bandwidthctl "Test VM" --name Limit --add disk --limit 10
Changes the limit of bandwidth group Limit to 10 MB/s. If the VM is running the limit will be picked up
immediately.

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