VirtualBox

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

Last change on this file since 39694 was 39248, checked in by vboxsync, 13 years ago

Runtime: new guest OS type for Solaris 11
Frontends/VirtualBox: add new patterns for Solaris 11 guest OS type, reuse the icon
Frontends/VBoxManage: more details for "list ostypes"
Main/xml: make guest OS type in config file an arbitrary string (still validated/mapped in the old way in the settings code), remove hardcoded limit of 8 network adapters
Main/Global: move list of valid guest OS types into a single place, add function to get the network adapter limit for each chipset type
Main/Console+Machine+Snapshot+NetworkAdapter+Appliance+VirtualBox+Guest+SystemProperties: consistently use the appropriate network adapter limit so that ICH9 chipset can use 36 network adapters, adapt to cleaned up guest OS type handling, remove leftover rendundant guest OS mapping, whitespace
Network/NAT: release log message cosmetics, allow unlimited number of instances, fix maxconn clamping
Network/PCNet+VirtioNet+E1000: allow unlimited number of instances

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