VirtualBox

source: vbox/trunk/src/VBox/Main/MachineImpl.cpp@ 1073

Last change on this file since 1073 was 1071, checked in by vboxsync, 18 years ago

fix Vista crash if sound is enabled; made audio LogRel consistent; winmm was never used, show dsound instead

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 304.0 KB
Line 
1/** @file
2 *
3 * VirtualBox COM class implementation
4 */
5
6/*
7 * Copyright (C) 2006 InnoTek Systemberatung GmbH
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License as published by the Free Software Foundation,
13 * in version 2 as it comes in the "COPYING" file of the VirtualBox OSE
14 * distribution. VirtualBox OSE is distributed in the hope that it will
15 * be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * If you received this file as part of a commercial VirtualBox
18 * distribution, then only the terms of your commercial VirtualBox
19 * license agreement apply instead of the previous paragraph.
20 */
21
22#if defined(__WIN__)
23#elif defined(__LINUX__)
24#endif
25
26#ifdef VBOX_WITH_SYS_V_IPC_SESSION_WATCHER
27# include <errno.h>
28# include <sys/types.h>
29# include <sys/stat.h>
30# include <sys/ipc.h>
31# include <sys/sem.h>
32#endif
33
34#include "VirtualBoxImpl.h"
35#include "MachineImpl.h"
36#include "HardDiskImpl.h"
37#include "HostDVDDriveImpl.h"
38#include "HostFloppyDriveImpl.h"
39#include "ProgressImpl.h"
40#include "HardDiskAttachmentImpl.h"
41#include "USBControllerImpl.h"
42#include "HostImpl.h"
43#include "SystemPropertiesImpl.h"
44#include "SharedFolderImpl.h"
45#include "GuestOSTypeImpl.h"
46#include "VirtualBoxErrorInfoImpl.h"
47
48#include "USBProxyService.h"
49
50#include "Logging.h"
51
52#include <stdio.h>
53#include <stdlib.h>
54#include <VBox/err.h>
55#include <VBox/cfgldr.h>
56#include <iprt/path.h>
57#include <iprt/dir.h>
58#include <iprt/asm.h>
59#include <iprt/process.h>
60#include <VBox/param.h>
61
62#include <algorithm>
63
64#if defined(__WIN__) || defined(__OS2__)
65#define HOSTSUFF_EXE ".exe"
66#else /* !__WIN__ */
67#define HOSTSUFF_EXE ""
68#endif /* !__WIN__ */
69
70// defines / prototypes
71/////////////////////////////////////////////////////////////////////////////
72
73/**
74 * Local mutability check macro for Machine implementation only.
75 */
76#define CHECK_SETTER() \
77 if (!isMutable()) \
78 return setError (E_ACCESSDENIED, tr ("The machine is not mutable"));
79
80// globals
81/////////////////////////////////////////////////////////////////////////////
82
83/**
84 * @note The template is NOT completely valid according to VBOX_XML_SCHEMA
85 * (when loading a newly created settings file, validation will be turned off)
86 */
87static const char DefaultMachineConfig[] =
88{
89 "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>" RTFILE_LINEFEED
90 "<!-- InnoTek VirtualBox Machine Configuration -->" RTFILE_LINEFEED
91 "<VirtualBox xmlns=\"" VBOX_XML_NAMESPACE "\" "
92 "version=\"" VBOX_XML_VERSION "-" VBOX_XML_PLATFORM "\">" RTFILE_LINEFEED
93 "</VirtualBox>" RTFILE_LINEFEED
94};
95
96/**
97 * Progress callback handler for lengthy operations
98 * (corresponds to the FNRTPROGRESS typedef).
99 *
100 * @param uPercentage Completetion precentage (0-100).
101 * @param pvUser Pointer to the Progress instance.
102 */
103static DECLCALLBACK(int) progressCallback (unsigned uPercentage, void *pvUser)
104{
105 Progress *progress = static_cast <Progress *> (pvUser);
106
107 /* update the progress object */
108 if (progress)
109 progress->notifyProgress (uPercentage);
110
111 return VINF_SUCCESS;
112}
113
114/////////////////////////////////////////////////////////////////////////////
115// Machine::Data structure
116/////////////////////////////////////////////////////////////////////////////
117
118Machine::Data::Data()
119{
120 mRegistered = FALSE;
121 /* mUuid is initialized in Machine::init() */
122
123 mMachineState = MachineState_PoweredOff;
124 RTTIMESPEC time;
125 mLastStateChange = RTTimeSpecGetMilli (RTTimeNow (&time));
126 mCurrentStateModified = TRUE;
127 mHandleCfgFile = NIL_RTFILE;
128
129 mSession.mPid = NIL_RTPROCESS;
130 mSession.mState = SessionState_SessionClosed;
131}
132
133Machine::Data::~Data()
134{
135}
136
137/////////////////////////////////////////////////////////////////////////////
138// Machine::UserData structure
139/////////////////////////////////////////////////////////////////////////////
140
141Machine::UserData::UserData()
142{
143 /* default values for a newly created machine */
144
145 mNameSync = TRUE;
146
147 /* mName, mOSType, mSnapshotFolder, mSnapshotFolderFull are initialized in
148 * Machine::init() */
149}
150
151Machine::UserData::~UserData()
152{
153}
154
155/////////////////////////////////////////////////////////////////////////////
156// Machine::HWData structure
157/////////////////////////////////////////////////////////////////////////////
158
159Machine::HWData::HWData()
160{
161 /* default values for a newly created machine */
162 mMemorySize = 128;
163 mVRAMSize = 8;
164 mHWVirtExEnabled = TriStateBool_False;
165
166 /* default boot order: floppy - DVD - HDD */
167 mBootOrder [0] = DeviceType_FloppyDevice;
168 mBootOrder [1] = DeviceType_DVDDevice;
169 mBootOrder [2] = DeviceType_HardDiskDevice;
170 for (size_t i = 3; i < ELEMENTS (mBootOrder); i++)
171 mBootOrder [i] = DeviceType_NoDevice;
172
173 mClipboardMode = ClipboardMode_ClipDisabled;
174}
175
176Machine::HWData::~HWData()
177{
178}
179
180bool Machine::HWData::operator== (const HWData &that) const
181{
182 if (this == &that)
183 return true;
184
185 if (mMemorySize != that.mMemorySize ||
186 mVRAMSize != that.mVRAMSize ||
187 mHWVirtExEnabled != that.mHWVirtExEnabled ||
188 mClipboardMode != that.mClipboardMode)
189 return false;
190
191 for (size_t i = 0; i < ELEMENTS (mBootOrder); ++ i)
192 if (mBootOrder [i] != that.mBootOrder [i])
193 return false;
194
195 if (mSharedFolders.size() != that.mSharedFolders.size())
196 return false;
197
198 if (mSharedFolders.size() == 0)
199 return true;
200
201 /* Make copies to speed up comparison */
202 SharedFolderList folders = mSharedFolders;
203 SharedFolderList thatFolders = that.mSharedFolders;
204
205 SharedFolderList::iterator it = folders.begin();
206 while (it != folders.end())
207 {
208 bool found = false;
209 SharedFolderList::iterator thatIt = thatFolders.begin();
210 while (thatIt != thatFolders.end())
211 {
212 if ((*it)->name() == (*thatIt)->name() &&
213 RTPathCompare (Utf8Str ((*it)->hostPath()),
214 Utf8Str ((*thatIt)->hostPath())) == 0)
215 {
216 thatFolders.erase (thatIt);
217 found = true;
218 break;
219 }
220 else
221 ++ thatIt;
222 }
223 if (found)
224 it = folders.erase (it);
225 else
226 return false;
227 }
228
229 Assert (folders.size() == 0 && thatFolders.size() == 0);
230
231 return true;
232}
233
234/////////////////////////////////////////////////////////////////////////////
235// Machine::HDData structure
236/////////////////////////////////////////////////////////////////////////////
237
238Machine::HDData::HDData()
239{
240 /* default values for a newly created machine */
241 mHDAttachmentsChanged = false;
242}
243
244Machine::HDData::~HDData()
245{
246}
247
248bool Machine::HDData::operator== (const HDData &that) const
249{
250 if (this == &that)
251 return true;
252
253 if (mHDAttachments.size() != that.mHDAttachments.size())
254 return false;
255
256 if (mHDAttachments.size() == 0)
257 return true;
258
259 /* Make copies to speed up comparison */
260 HDAttachmentList atts = mHDAttachments;
261 HDAttachmentList thatAtts = that.mHDAttachments;
262
263 HDAttachmentList::iterator it = atts.begin();
264 while (it != atts.end())
265 {
266 bool found = false;
267 HDAttachmentList::iterator thatIt = thatAtts.begin();
268 while (thatIt != thatAtts.end())
269 {
270 if ((*it)->deviceNumber() == (*thatIt)->deviceNumber() &&
271 (*it)->controller() == (*thatIt)->controller() &&
272 (*it)->hardDisk().equalsTo ((*thatIt)->hardDisk()))
273 {
274 thatAtts.erase (thatIt);
275 found = true;
276 break;
277 }
278 else
279 ++ thatIt;
280 }
281 if (found)
282 it = atts.erase (it);
283 else
284 return false;
285 }
286
287 Assert (atts.size() == 0 && thatAtts.size() == 0);
288
289 return true;
290}
291
292/////////////////////////////////////////////////////////////////////////////
293// Machine class
294/////////////////////////////////////////////////////////////////////////////
295
296// constructor / destructor
297/////////////////////////////////////////////////////////////////////////////
298
299Machine::Machine() : mType (IsMachine) {}
300
301Machine::~Machine() {}
302
303HRESULT Machine::FinalConstruct()
304{
305 LogFlowThisFunc (("\n"));
306 return S_OK;
307}
308
309void Machine::FinalRelease()
310{
311 LogFlowThisFunc (("\n"));
312 uninit();
313}
314
315/**
316 * Initializes the instance.
317 *
318 * @param aParent Associated parent object
319 * @param aConfigFile Local file system path to the VM settings file (can
320 * be relative to the VirtualBox config directory).
321 * @param aMode Init_New, Init_Existing or Init_Registered
322 * @param aName name for the machine when aMode is Init_New
323 * (ignored otherwise)
324 * @param aNameSync |TRUE| to authomatically sync settings dir and file
325 * name with the machine name. |FALSE| is used for legacy
326 * machines where the file name is specified by the
327 * user and should never change. Used only in Init_New
328 * mode (ignored otherwise).
329 * @param aId UUID of the machine (used only for consistency
330 * check when aMode is Init_Registered; must match UUID
331 * stored in the settings file).
332 *
333 * @return Success indicator. if not S_OK, the machine object is invalid
334 */
335HRESULT Machine::init (VirtualBox *aParent, const BSTR aConfigFile,
336 InitMode aMode, const BSTR aName /* = NULL */,
337 BOOL aNameSync /* = TRUE */,
338 const Guid *aId /* = NULL */)
339{
340 LogFlowThisFuncEnter();
341 LogFlowThisFunc (("aConfigFile='%ls', aMode=%d\n", aConfigFile, aMode));
342
343 AssertReturn (aParent, E_INVALIDARG);
344 AssertReturn (aConfigFile, E_INVALIDARG);
345 AssertReturn (aMode != Init_New || (aName != NULL && *aName != '\0'),
346 E_INVALIDARG);
347 AssertReturn (aMode != Init_Registered || aId != NULL, E_FAIL);
348
349 /* Enclose the state transition NotReady->InInit->Ready */
350 AutoInitSpan autoInitSpan (this);
351 AssertReturn (autoInitSpan.isOk(), E_UNEXPECTED);
352
353 HRESULT rc = S_OK;
354
355 /* share the parent weakly */
356 unconst (mParent) = aParent;
357
358 /* register with parent early, since uninit() will unconditionally
359 * unregister on failure */
360 mParent->addDependentChild (this);
361
362 /* create machine data structures */
363 mData.allocate();
364 mSSData.allocate();
365
366 mUserData.allocate();
367 mHWData.allocate();
368 mHDData.allocate();
369
370 char configFileFull [RTPATH_MAX] = {0};
371
372 /* memorize the config file name (as provided) */
373 mData->mConfigFile = aConfigFile;
374
375 /* get the full file name */
376 int vrc = RTPathAbsEx (mParent->homeDir(), Utf8Str (aConfigFile),
377 configFileFull, sizeof (configFileFull));
378 if (VBOX_FAILURE (vrc))
379 return setError (E_FAIL,
380 tr ("Invalid settings file name: '%ls' (%Vrc)"),
381 aConfigFile, vrc);
382 mData->mConfigFileFull = configFileFull;
383
384 mData->mAccessible = TRUE;
385
386 if (aMode != Init_New)
387 {
388 /* lock the settings file */
389 rc = lockConfig();
390
391 if (aMode == Init_Registered && FAILED (rc))
392 {
393 /* If the machine is registered, then, instead of returning a
394 * failure, we mark it as inaccessible and set the result to
395 * success to give it a try later */
396 mData->mAccessible = FALSE;
397 /* fetch the current error info */
398 mData->mAccessError = com::ErrorInfo();
399 LogWarning (("Machine {%Vuuid} is inaccessible! [%ls]\n",
400 mData->mUuid.raw(),
401 mData->mAccessError.getText().raw()));
402 rc = S_OK;
403 }
404 }
405 else
406 {
407 /* check for the file existence */
408 RTFILE f = NIL_RTFILE;
409 int vrc = RTFileOpen (&f, configFileFull, RTFILE_O_READ);
410 if (VBOX_SUCCESS (vrc) || vrc == VERR_SHARING_VIOLATION)
411 {
412 rc = setError (E_FAIL,
413 tr ("Settings file '%s' already exists"), configFileFull);
414 if (VBOX_SUCCESS (vrc))
415 RTFileClose (f);
416 }
417 else
418 {
419 if (vrc != VERR_FILE_NOT_FOUND && vrc != VERR_PATH_NOT_FOUND)
420 rc = setError (E_FAIL,
421 tr ("Invalid settings file name: '%ls' (%Vrc)"),
422 mData->mConfigFileFull.raw(), vrc);
423 }
424 }
425
426 CheckComRCReturnRC (rc);
427
428 /* initialize mOSType */
429 mUserData->mOSType = mParent->getUnknownOSType();
430
431 /* create associated BIOS settings object */
432 unconst (mBIOSSettings).createObject();
433 mBIOSSettings->init(this);
434
435#ifdef VBOX_VRDP
436 /* create an associated VRDPServer object (default is disabled) */
437 unconst (mVRDPServer).createObject();
438 mVRDPServer->init(this);
439#endif
440
441 /* create an associated DVD drive object */
442 unconst (mDVDDrive).createObject();
443 mDVDDrive->init (this);
444
445 /* create an associated floppy drive object */
446 unconst (mFloppyDrive).createObject();
447 mFloppyDrive->init (this);
448
449 /* create the audio adapter object (always present, default is disabled) */
450 unconst (mAudioAdapter).createObject();
451 mAudioAdapter->init(this);
452
453 /* create the USB controller object (always present, default is disabled) */
454 unconst (mUSBController).createObject();
455 mUSBController->init(this);
456
457 /* create associated network adapter objects */
458 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
459 {
460 unconst (mNetworkAdapters [slot]).createObject();
461 mNetworkAdapters [slot]->init (this, slot);
462 }
463
464 if (aMode == Init_Registered)
465 {
466 /* store the supplied UUID (will be used to check for UUID consistency
467 * in loadSettings() */
468 unconst (mData->mUuid) = *aId;
469 /* try to load settings only if the settings file is accessible */
470 if (mData->mAccessible)
471 rc = registeredInit();
472 }
473 else
474 {
475 if (aMode != Init_New)
476 {
477 rc = loadSettings (false /* aRegistered */);
478 }
479 else
480 {
481 /* create the machine UUID */
482 unconst (mData->mUuid).create();
483
484 /* initialize the default snapshots folder */
485 rc = COMSETTER(SnapshotFolder) (NULL);
486 AssertComRC (rc);
487
488 /* memorize the provided new machine's name */
489 mUserData->mName = aName;
490 mUserData->mNameSync = aNameSync;
491 }
492
493 /* commit all changes made during the initialization */
494 if (SUCCEEDED (rc))
495 commit();
496 }
497
498 /* Confirm a successful initialization when it's the case */
499 if (SUCCEEDED (rc))
500 {
501 if (mData->mAccessible)
502 autoInitSpan.setSucceeded();
503 else
504 autoInitSpan.setLimited();
505 }
506
507 LogFlowThisFunc (("mName='%ls', mRegistered=%RTbool, mAccessible=%RTbool "
508 "rc=%08X\n",
509 mUserData->mName.raw(), mData->mRegistered,
510 mData->mAccessible, rc));
511
512 LogFlowThisFuncLeave();
513
514 return rc;
515}
516
517/**
518 * Initializes the registered machine by loading the settings file.
519 * This method is separated from #init() in order to make it possible to
520 * retry the operation after VirtualBox startup instead of refusing to
521 * startup the whole VirtualBox server in case if the settings file of some
522 * registered VM is invalid or inaccessible.
523 *
524 * @note Must be always called from this object's write lock
525 * (unless called from #init() that doesn't need any locking).
526 * @note Locks the mUSBController method for writing.
527 * @note Subclasses must not call this method.
528 */
529HRESULT Machine::registeredInit()
530{
531 AssertReturn (mType == IsMachine, E_FAIL);
532 AssertReturn (!mData->mUuid.isEmpty(), E_FAIL);
533
534 HRESULT rc = S_OK;
535
536 if (!mData->mAccessible)
537 rc = lockConfig();
538
539 /* Temporarily reset the registered flag in order to let setters potentially
540 * called from loadSettings() succeed (isMutable() used in all setters
541 * will return FALSE for a Machine instance if mRegistered is TRUE). */
542 mData->mRegistered = FALSE;
543
544 if (SUCCEEDED (rc))
545 {
546 rc = loadSettings (true /* aRegistered */);
547
548 if (FAILED (rc))
549 unlockConfig();
550 }
551
552 if (SUCCEEDED (rc))
553 {
554 mData->mAccessible = TRUE;
555
556 /* commit all changes made during loading the settings file */
557 commit();
558
559 /* VirtualBox will not call trySetRegistered(), so
560 * inform the USB proxy about all attached USB filters */
561 mUSBController->onMachineRegistered (TRUE);
562 }
563 else
564 {
565 /* If the machine is registered, then, instead of returning a
566 * failure, we mark it as inaccessible and set the result to
567 * success to give it a try later */
568 mData->mAccessible = FALSE;
569 /* fetch the current error info */
570 mData->mAccessError = com::ErrorInfo();
571 LogWarning (("Machine {%Vuuid} is inaccessible! [%ls]\n",
572 mData->mUuid.raw(),
573 mData->mAccessError.getText().raw()));
574
575 /* rollback all changes */
576 rollback (false /* aNotify */);
577
578 rc = S_OK;
579 }
580
581 /* Restore the registered flag (even on failure) */
582 mData->mRegistered = TRUE;
583
584 return rc;
585}
586
587/**
588 * Uninitializes the instance.
589 * Called either from FinalRelease() or by the parent when it gets destroyed.
590 *
591 * @note The caller of this method must make sure that this object
592 * a) doesn't have active callers on the current thread and b) is not locked
593 * by the current thread; otherwise uninit() will hang either a) due to
594 * AutoUninitSpan waiting for a number of calls to drop to zero or b) due to
595 * a dead-lock caused by this thread waiting for all callers on the other
596 * threads are are done but preventing them from doing so by holding a lock.
597 */
598void Machine::uninit()
599{
600 LogFlowThisFuncEnter();
601
602 Assert (!isLockedOnCurrentThread());
603
604 /* Enclose the state transition Ready->InUninit->NotReady */
605 AutoUninitSpan autoUninitSpan (this);
606 if (autoUninitSpan.uninitDone())
607 return;
608
609 Assert (mType == IsMachine);
610 Assert (!!mData && !!mUserData && !!mHWData && !!mHDData && !!mSSData);
611
612 LogFlowThisFunc (("initFailed()=%d\n", autoUninitSpan.initFailed()));
613 LogFlowThisFunc (("mRegistered=%d\n", mData->mRegistered));
614
615 /*
616 * Enter this object's lock because there may be a SessionMachine instance
617 * somewhere around, that shares our data and lock but doesn't use our
618 * addCaller()/removeCaller(), and it may be also accessing the same
619 * data members. mParent lock is necessary as well because of
620 * SessionMachine::uninit(), etc.
621 */
622 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
623
624 if (!mData->mSession.mMachine.isNull())
625 {
626 /*
627 * Theoretically, this can only happen if the VirtualBox server has
628 * been terminated while there were clients running that owned open
629 * direct sessions. Since in this case we are definitely called by
630 * VirtualBox::uninit(), we may be sure that SessionMachine::uninit()
631 * won't happen on the client watcher thread (because it does
632 * VirtualBox::addCaller() for the duration of the
633 * SessionMachine::checkForDeath() call, so that VirtualBox::uninit()
634 * cannot happen until the VirtualBox caller is released). This is
635 * important, because SessionMachine::uninit() cannot correctly operate
636 * after we return from this method (it expects the Machine instance
637 * is still valid). We'll call it ourselves below.
638 */
639 LogWarningThisFunc (("Session machine is not NULL (%p), "
640 "the direct session is still open!\n",
641 (SessionMachine *) mData->mSession.mMachine));
642
643 if (mData->mMachineState >= MachineState_Running)
644 {
645 LogWarningThisFunc (("Setting state to Aborted!\n"));
646 /* set machine state using SessionMachine reimplementation */
647 static_cast <Machine *> (mData->mSession.mMachine)
648 ->setMachineState (MachineState_Aborted);
649 }
650
651 /*
652 * Uninitialize SessionMachine using public uninit() to indicate
653 * an unexpected uninitialization.
654 */
655 mData->mSession.mMachine->uninit();
656 /* SessionMachine::uninit() must set mSession.mMachine to null */
657 Assert (mData->mSession.mMachine.isNull());
658 }
659
660 /* the lock is no more necessary (SessionMachine is uninitialized) */
661 alock.leave();
662
663 /* make sure the configuration is unlocked */
664 unlockConfig();
665
666 if (isModified())
667 {
668 LogWarningThisFunc (("Discarding unsaved settings changes!\n"));
669 rollback (false /* aNotify */);
670 }
671
672 uninitDataAndChildObjects();
673
674 mParent->removeDependentChild (this);
675
676 LogFlowThisFuncLeave();
677}
678
679// IMachine properties
680/////////////////////////////////////////////////////////////////////////////
681
682STDMETHODIMP Machine::COMGETTER(Parent) (IVirtualBox **aParent)
683{
684 if (!aParent)
685 return E_POINTER;
686
687 AutoLimitedCaller autoCaller (this);
688 CheckComRCReturnRC (autoCaller.rc());
689
690 /* mParent is constant during life time, no need to lock */
691 mParent.queryInterfaceTo (aParent);
692
693 return S_OK;
694}
695
696STDMETHODIMP Machine::COMGETTER(Accessible) (BOOL *aAccessible)
697{
698 if (!aAccessible)
699 return E_POINTER;
700
701 AutoLimitedCaller autoCaller (this);
702 CheckComRCReturnRC (autoCaller.rc());
703
704 AutoLock alock (this);
705
706 HRESULT rc = S_OK;
707
708 if (!mData->mAccessible)
709 {
710 /* try to initialize the VM once more if not accessible */
711
712 AutoReadySpan autoReadySpan (this);
713 AssertReturn (autoReadySpan.isOk(), E_FAIL);
714
715 rc = registeredInit();
716
717 if (mData->mAccessible)
718 autoReadySpan.setSucceeded();
719 }
720
721 if (SUCCEEDED (rc))
722 *aAccessible = mData->mAccessible;
723
724 return rc;
725}
726
727STDMETHODIMP Machine::COMGETTER(AccessError) (IVirtualBoxErrorInfo **aAccessError)
728{
729 if (!aAccessError)
730 return E_POINTER;
731
732 AutoLimitedCaller autoCaller (this);
733 CheckComRCReturnRC (autoCaller.rc());
734
735 AutoReaderLock alock (this);
736
737 if (mData->mAccessible || !mData->mAccessError.isBasicAvailable())
738 {
739 /* return shortly */
740 aAccessError = NULL;
741 return S_OK;
742 }
743
744 HRESULT rc = S_OK;
745
746 ComObjPtr <VirtualBoxErrorInfo> errorInfo;
747 rc = errorInfo.createObject();
748 if (SUCCEEDED (rc))
749 {
750 errorInfo->init (mData->mAccessError.getResultCode(),
751 mData->mAccessError.getInterfaceID(),
752 mData->mAccessError.getComponent(),
753 mData->mAccessError.getText());
754 rc = errorInfo.queryInterfaceTo (aAccessError);
755 }
756
757 return rc;
758}
759
760STDMETHODIMP Machine::COMGETTER(Name) (BSTR *aName)
761{
762 if (!aName)
763 return E_POINTER;
764
765 AutoCaller autoCaller (this);
766 CheckComRCReturnRC (autoCaller.rc());
767
768 AutoReaderLock alock (this);
769
770 mUserData->mName.cloneTo (aName);
771
772 return S_OK;
773}
774
775STDMETHODIMP Machine::COMSETTER(Name) (INPTR BSTR aName)
776{
777 if (!aName)
778 return E_INVALIDARG;
779
780 if (!*aName)
781 return setError (E_INVALIDARG,
782 tr ("Machine name cannot be empty"));
783
784 AutoCaller autoCaller (this);
785 CheckComRCReturnRC (autoCaller.rc());
786
787 AutoLock alock (this);
788
789 CHECK_SETTER();
790
791 mUserData.backup();
792 mUserData->mName = aName;
793
794 return S_OK;
795}
796
797STDMETHODIMP Machine::COMGETTER(Id) (GUIDPARAMOUT aId)
798{
799 if (!aId)
800 return E_POINTER;
801
802 AutoLimitedCaller autoCaller (this);
803 CheckComRCReturnRC (autoCaller.rc());
804
805 AutoReaderLock alock (this);
806
807 mData->mUuid.cloneTo (aId);
808
809 return S_OK;
810}
811
812STDMETHODIMP Machine::COMGETTER(OSType) (IGuestOSType **aOSType)
813{
814 if (!aOSType)
815 return E_POINTER;
816
817 AutoCaller autoCaller (this);
818 CheckComRCReturnRC (autoCaller.rc());
819
820 AutoReaderLock alock (this);
821
822 mUserData->mOSType.queryInterfaceTo (aOSType);
823
824 return S_OK;
825}
826
827STDMETHODIMP Machine::COMSETTER(OSType) (IGuestOSType *aOSType)
828{
829 if (!aOSType)
830 return E_INVALIDARG;
831
832 AutoCaller autoCaller (this);
833 CheckComRCReturnRC (autoCaller.rc());
834
835 AutoLock alock (this);
836
837 CHECK_SETTER();
838
839 mUserData.backup();
840 mUserData->mOSType = aOSType;
841
842 return S_OK;
843}
844
845STDMETHODIMP Machine::COMGETTER(MemorySize) (ULONG *memorySize)
846{
847 if (!memorySize)
848 return E_POINTER;
849
850 AutoCaller autoCaller (this);
851 CheckComRCReturnRC (autoCaller.rc());
852
853 AutoReaderLock alock (this);
854
855 *memorySize = mHWData->mMemorySize;
856
857 return S_OK;
858}
859
860STDMETHODIMP Machine::COMSETTER(MemorySize) (ULONG memorySize)
861{
862 /* check RAM limits */
863 if (memorySize < SchemaDefs::MinGuestRAM ||
864 memorySize > SchemaDefs::MaxGuestRAM)
865 return setError (E_INVALIDARG,
866 tr ("Invalid RAM size: %lu MB (must be in range [%lu, %lu] MB)"),
867 memorySize, SchemaDefs::MinGuestRAM, SchemaDefs::MaxGuestRAM);
868
869 AutoCaller autoCaller (this);
870 CheckComRCReturnRC (autoCaller.rc());
871
872 AutoLock alock (this);
873
874 CHECK_SETTER();
875
876 mHWData.backup();
877 mHWData->mMemorySize = memorySize;
878
879 return S_OK;
880}
881
882STDMETHODIMP Machine::COMGETTER(VRAMSize) (ULONG *memorySize)
883{
884 if (!memorySize)
885 return E_POINTER;
886
887 AutoCaller autoCaller (this);
888 CheckComRCReturnRC (autoCaller.rc());
889
890 AutoReaderLock alock (this);
891
892 *memorySize = mHWData->mVRAMSize;
893
894 return S_OK;
895}
896
897STDMETHODIMP Machine::COMSETTER(VRAMSize) (ULONG memorySize)
898{
899 /* check VRAM limits */
900 if (memorySize < SchemaDefs::MinGuestVRAM ||
901 memorySize > SchemaDefs::MaxGuestVRAM)
902 return setError (E_INVALIDARG,
903 tr ("Invalid VRAM size: %lu MB (must be in range [%lu, %lu] MB)"),
904 memorySize, SchemaDefs::MinGuestVRAM, SchemaDefs::MaxGuestVRAM);
905
906 AutoCaller autoCaller (this);
907 CheckComRCReturnRC (autoCaller.rc());
908
909 AutoLock alock (this);
910
911 CHECK_SETTER();
912
913 mHWData.backup();
914 mHWData->mVRAMSize = memorySize;
915
916 return S_OK;
917}
918
919STDMETHODIMP Machine::COMGETTER(BIOSSettings)(IBIOSSettings **biosSettings)
920{
921 if (!biosSettings)
922 return E_POINTER;
923
924 AutoCaller autoCaller (this);
925 CheckComRCReturnRC (autoCaller.rc());
926
927 /* mBIOSSettings is constant during life time, no need to lock */
928 mBIOSSettings.queryInterfaceTo (biosSettings);
929
930 return S_OK;
931}
932
933STDMETHODIMP Machine::COMGETTER(HWVirtExEnabled)(TriStateBool_T *enabled)
934{
935 if (!enabled)
936 return E_POINTER;
937
938 AutoCaller autoCaller (this);
939 CheckComRCReturnRC (autoCaller.rc());
940
941 AutoReaderLock alock (this);
942
943 *enabled = mHWData->mHWVirtExEnabled;
944
945 return S_OK;
946}
947
948STDMETHODIMP Machine::COMSETTER(HWVirtExEnabled)(TriStateBool_T enable)
949{
950 AutoCaller autoCaller (this);
951 CheckComRCReturnRC (autoCaller.rc());
952
953 AutoLock alock (this);
954
955 CHECK_SETTER();
956
957 /** @todo check validity! */
958
959 mHWData.backup();
960 mHWData->mHWVirtExEnabled = enable;
961
962 return S_OK;
963}
964
965STDMETHODIMP Machine::COMGETTER(SnapshotFolder) (BSTR *aSnapshotFolder)
966{
967 if (!aSnapshotFolder)
968 return E_POINTER;
969
970 AutoCaller autoCaller (this);
971 CheckComRCReturnRC (autoCaller.rc());
972
973 AutoReaderLock alock (this);
974
975 mUserData->mSnapshotFolderFull.cloneTo (aSnapshotFolder);
976
977 return S_OK;
978}
979
980STDMETHODIMP Machine::COMSETTER(SnapshotFolder) (INPTR BSTR aSnapshotFolder)
981{
982 /// @todo (r=dmik):
983 // 1. Allow to change the name of the snapshot folder containing snapshots
984 // 2. Rename the folder on disk instead of just changing the property
985 // value (to be smart and not to leave garbage). Note that it cannot be
986 // done here because the change may be rolled back. Thus, the right
987 // place is #saveSettings().
988
989 AutoCaller autoCaller (this);
990 CheckComRCReturnRC (autoCaller.rc());
991
992 AutoLock alock (this);
993
994 CHECK_SETTER();
995
996 if (!mData->mCurrentSnapshot.isNull())
997 return setError (E_FAIL,
998 tr ("The snapshot folder of a machine with snapshots cannot "
999 "be changed (please discard all snapshots first)"));
1000
1001 Utf8Str snapshotFolder = aSnapshotFolder;
1002
1003 if (snapshotFolder.isEmpty())
1004 {
1005 if (isInOwnDir())
1006 {
1007 /* the default snapshots folder is 'Snapshots' in the machine dir */
1008 snapshotFolder = Utf8Str ("Snapshots");
1009 }
1010 else
1011 {
1012 /* the default snapshots folder is {UUID}, for backwards
1013 * compatibility and to resolve conflicts */
1014 snapshotFolder = Utf8StrFmt ("{%Vuuid}", mData->mUuid.raw());
1015 }
1016 }
1017
1018 int vrc = calculateFullPath (snapshotFolder, snapshotFolder);
1019 if (VBOX_FAILURE (vrc))
1020 return setError (E_FAIL,
1021 tr ("Invalid snapshot folder: '%ls' (%Vrc)"),
1022 aSnapshotFolder, vrc);
1023
1024 mUserData.backup();
1025 mUserData->mSnapshotFolder = aSnapshotFolder;
1026 mUserData->mSnapshotFolderFull = snapshotFolder;
1027
1028 return S_OK;
1029}
1030
1031STDMETHODIMP Machine::COMGETTER(HardDiskAttachments) (IHardDiskAttachmentCollection **attachments)
1032{
1033 if (!attachments)
1034 return E_POINTER;
1035
1036 AutoCaller autoCaller (this);
1037 CheckComRCReturnRC (autoCaller.rc());
1038
1039 AutoReaderLock alock (this);
1040
1041 ComObjPtr <HardDiskAttachmentCollection> collection;
1042 collection.createObject();
1043 collection->init (mHDData->mHDAttachments);
1044 collection.queryInterfaceTo (attachments);
1045
1046 return S_OK;
1047}
1048
1049STDMETHODIMP Machine::COMGETTER(VRDPServer)(IVRDPServer **vrdpServer)
1050{
1051#ifdef VBOX_VRDP
1052 if (!vrdpServer)
1053 return E_POINTER;
1054
1055 AutoCaller autoCaller (this);
1056 CheckComRCReturnRC (autoCaller.rc());
1057
1058 AutoReaderLock alock (this);
1059
1060 Assert (!!mVRDPServer);
1061 mVRDPServer.queryInterfaceTo (vrdpServer);
1062
1063 return S_OK;
1064#else
1065 return E_NOTIMPL;
1066#endif
1067}
1068
1069STDMETHODIMP Machine::COMGETTER(DVDDrive) (IDVDDrive **dvdDrive)
1070{
1071 if (!dvdDrive)
1072 return E_POINTER;
1073
1074 AutoCaller autoCaller (this);
1075 CheckComRCReturnRC (autoCaller.rc());
1076
1077 AutoReaderLock alock (this);
1078
1079 Assert (!!mDVDDrive);
1080 mDVDDrive.queryInterfaceTo (dvdDrive);
1081 return S_OK;
1082}
1083
1084STDMETHODIMP Machine::COMGETTER(FloppyDrive) (IFloppyDrive **floppyDrive)
1085{
1086 if (!floppyDrive)
1087 return E_POINTER;
1088
1089 AutoCaller autoCaller (this);
1090 CheckComRCReturnRC (autoCaller.rc());
1091
1092 AutoReaderLock alock (this);
1093
1094 Assert (!!mFloppyDrive);
1095 mFloppyDrive.queryInterfaceTo (floppyDrive);
1096 return S_OK;
1097}
1098
1099STDMETHODIMP Machine::COMGETTER(AudioAdapter)(IAudioAdapter **audioAdapter)
1100{
1101 if (!audioAdapter)
1102 return E_POINTER;
1103
1104 AutoCaller autoCaller (this);
1105 CheckComRCReturnRC (autoCaller.rc());
1106
1107 AutoReaderLock alock (this);
1108
1109 mAudioAdapter.queryInterfaceTo (audioAdapter);
1110 return S_OK;
1111}
1112
1113STDMETHODIMP Machine::COMGETTER(USBController)(IUSBController * *a_ppUSBController)
1114{
1115#ifdef VBOX_WITH_USB
1116 if (!a_ppUSBController)
1117 return E_POINTER;
1118
1119 AutoCaller autoCaller (this);
1120 CheckComRCReturnRC (autoCaller.rc());
1121
1122 HRESULT rc = mParent->host()->checkUSBProxyService();
1123 CheckComRCReturnRC (rc);
1124
1125 AutoReaderLock alock (this);
1126
1127 mUSBController.queryInterfaceTo (a_ppUSBController);
1128 return S_OK;
1129#else
1130 /* Note: The GUI depends on this method returning E_NOTIMPL with no
1131 * extended error info to indicate that USB is simply not available
1132 * (w/o treting it as a failure), for example, as in OSE */
1133 return E_NOTIMPL;
1134#endif
1135}
1136
1137STDMETHODIMP Machine::COMGETTER(SettingsFilePath) (BSTR *filePath)
1138{
1139 if (!filePath)
1140 return E_POINTER;
1141
1142 AutoLimitedCaller autoCaller (this);
1143 CheckComRCReturnRC (autoCaller.rc());
1144
1145 AutoReaderLock alock (this);
1146
1147 mData->mConfigFileFull.cloneTo (filePath);
1148 return S_OK;
1149}
1150
1151STDMETHODIMP Machine::COMGETTER(SettingsModified) (BOOL *modified)
1152{
1153 if (!modified)
1154 return E_POINTER;
1155
1156 AutoCaller autoCaller (this);
1157 CheckComRCReturnRC (autoCaller.rc());
1158
1159 AutoLock alock (this);
1160
1161 CHECK_SETTER();
1162
1163 if (!isConfigLocked())
1164 {
1165 /*
1166 * if we're ready and isConfigLocked() is FALSE then it means
1167 * that no config file exists yet, so always return TRUE
1168 */
1169 *modified = TRUE;
1170 }
1171 else
1172 {
1173 *modified = isModified();
1174 }
1175
1176 return S_OK;
1177}
1178
1179STDMETHODIMP Machine::COMGETTER(SessionState) (SessionState_T *sessionState)
1180{
1181 if (!sessionState)
1182 return E_POINTER;
1183
1184 AutoCaller autoCaller (this);
1185 CheckComRCReturnRC (autoCaller.rc());
1186
1187 AutoReaderLock alock (this);
1188
1189 *sessionState = mData->mSession.mState;
1190
1191 return S_OK;
1192}
1193
1194STDMETHODIMP Machine::COMGETTER(State) (MachineState_T *machineState)
1195{
1196 if (!machineState)
1197 return E_POINTER;
1198
1199 AutoCaller autoCaller (this);
1200 CheckComRCReturnRC (autoCaller.rc());
1201
1202 AutoReaderLock alock (this);
1203
1204 *machineState = mData->mMachineState;
1205
1206 return S_OK;
1207}
1208
1209STDMETHODIMP Machine::COMGETTER(LastStateChange) (LONG64 *aLastStateChange)
1210{
1211 if (!aLastStateChange)
1212 return E_POINTER;
1213
1214 AutoCaller autoCaller (this);
1215 CheckComRCReturnRC (autoCaller.rc());
1216
1217 AutoReaderLock alock (this);
1218
1219 *aLastStateChange = mData->mLastStateChange;
1220
1221 return S_OK;
1222}
1223
1224STDMETHODIMP Machine::COMGETTER(StateFilePath) (BSTR *aStateFilePath)
1225{
1226 if (!aStateFilePath)
1227 return E_POINTER;
1228
1229 AutoCaller autoCaller (this);
1230 CheckComRCReturnRC (autoCaller.rc());
1231
1232 AutoReaderLock alock (this);
1233
1234 mSSData->mStateFilePath.cloneTo (aStateFilePath);
1235
1236 return S_OK;
1237}
1238
1239STDMETHODIMP Machine::COMGETTER(CurrentSnapshot) (ISnapshot **aCurrentSnapshot)
1240{
1241 if (!aCurrentSnapshot)
1242 return E_POINTER;
1243
1244 AutoCaller autoCaller (this);
1245 CheckComRCReturnRC (autoCaller.rc());
1246
1247 AutoReaderLock alock (this);
1248
1249 mData->mCurrentSnapshot.queryInterfaceTo (aCurrentSnapshot);
1250
1251 return S_OK;
1252}
1253
1254STDMETHODIMP Machine::COMGETTER(SnapshotCount) (ULONG *aSnapshotCount)
1255{
1256 if (!aSnapshotCount)
1257 return E_POINTER;
1258
1259 AutoCaller autoCaller (this);
1260 CheckComRCReturnRC (autoCaller.rc());
1261
1262 AutoReaderLock alock (this);
1263
1264 *aSnapshotCount = !mData->mFirstSnapshot ? 0 :
1265 mData->mFirstSnapshot->descendantCount() + 1 /* self */;
1266
1267 return S_OK;
1268}
1269
1270STDMETHODIMP Machine::COMGETTER(CurrentStateModified) (BOOL *aCurrentStateModified)
1271{
1272 if (!aCurrentStateModified)
1273 return E_POINTER;
1274
1275 AutoCaller autoCaller (this);
1276 CheckComRCReturnRC (autoCaller.rc());
1277
1278 AutoReaderLock alock (this);
1279
1280 /*
1281 * Note: for machines with no snapshots, we always return FALSE
1282 * (mData->mCurrentStateModified will be TRUE in this case, for historical
1283 * reasons :)
1284 */
1285
1286 *aCurrentStateModified = !mData->mFirstSnapshot ? FALSE :
1287 mData->mCurrentStateModified;
1288
1289 return S_OK;
1290}
1291
1292STDMETHODIMP
1293Machine::COMGETTER(SharedFolders) (ISharedFolderCollection **aSharedFolders)
1294{
1295 if (!aSharedFolders)
1296 return E_POINTER;
1297
1298 AutoCaller autoCaller (this);
1299 CheckComRCReturnRC (autoCaller.rc());
1300
1301 AutoReaderLock alock (this);
1302
1303 ComObjPtr <SharedFolderCollection> coll;
1304 coll.createObject();
1305 coll->init (mHWData->mSharedFolders);
1306 coll.queryInterfaceTo (aSharedFolders);
1307
1308 return S_OK;
1309}
1310
1311STDMETHODIMP
1312Machine::COMGETTER(ClipboardMode) (ClipboardMode_T *aClipboardMode)
1313{
1314 if (!aClipboardMode)
1315 return E_POINTER;
1316
1317 AutoCaller autoCaller (this);
1318 CheckComRCReturnRC (autoCaller.rc());
1319
1320 AutoReaderLock alock (this);
1321
1322 *aClipboardMode = mHWData->mClipboardMode;
1323
1324 return S_OK;
1325}
1326
1327STDMETHODIMP
1328Machine::COMSETTER(ClipboardMode) (ClipboardMode_T aClipboardMode)
1329{
1330 AutoCaller autoCaller (this);
1331 CheckComRCReturnRC (autoCaller.rc());
1332
1333 AutoLock alock (this);
1334
1335 CHECK_SETTER();
1336
1337 mHWData.backup();
1338 mHWData->mClipboardMode = aClipboardMode;
1339
1340 return S_OK;
1341}
1342
1343// IMachine methods
1344/////////////////////////////////////////////////////////////////////////////
1345
1346STDMETHODIMP Machine::SetBootOrder (ULONG aPosition, DeviceType_T aDevice)
1347{
1348 if (aPosition < 1 || aPosition > SchemaDefs::MaxBootPosition)
1349 return setError (E_INVALIDARG,
1350 tr ("Invalid boot position: %lu (must be in range [1, %lu])"),
1351 aPosition, SchemaDefs::MaxBootPosition);
1352
1353 if (aDevice == DeviceType_USBDevice)
1354 return setError (E_FAIL,
1355 tr ("Booting from USB devices is not currently supported"));
1356
1357 AutoCaller autoCaller (this);
1358 CheckComRCReturnRC (autoCaller.rc());
1359
1360 AutoLock alock (this);
1361
1362 CHECK_SETTER();
1363
1364 mHWData.backup();
1365 mHWData->mBootOrder [aPosition - 1] = aDevice;
1366
1367 return S_OK;
1368}
1369
1370STDMETHODIMP Machine::GetBootOrder (ULONG aPosition, DeviceType_T *aDevice)
1371{
1372 if (aPosition < 1 || aPosition > SchemaDefs::MaxBootPosition)
1373 return setError (E_INVALIDARG,
1374 tr ("Invalid boot position: %lu (must be in range [1, %lu])"),
1375 aPosition, SchemaDefs::MaxBootPosition);
1376
1377 AutoCaller autoCaller (this);
1378 CheckComRCReturnRC (autoCaller.rc());
1379
1380 AutoReaderLock alock (this);
1381
1382 *aDevice = mHWData->mBootOrder [aPosition - 1];
1383
1384 return S_OK;
1385}
1386
1387STDMETHODIMP Machine::AttachHardDisk (INPTR GUIDPARAM aId,
1388 DiskControllerType_T aCtl, LONG aDev)
1389{
1390 Guid id = aId;
1391
1392 if (id.isEmpty() ||
1393 aCtl == DiskControllerType_InvalidController ||
1394 aDev < 0 || aDev > 1)
1395 return E_INVALIDARG;
1396
1397 AutoCaller autoCaller (this);
1398 CheckComRCReturnRC (autoCaller.rc());
1399
1400 AutoLock alock (this);
1401
1402 CHECK_SETTER();
1403
1404 if (!mData->mRegistered)
1405 return setError (E_FAIL,
1406 tr ("Cannot attach hard disks to an unregistered machine"));
1407
1408 AssertReturn (mData->mMachineState != MachineState_Saved, E_FAIL);
1409
1410 if (mData->mMachineState >= MachineState_Running)
1411 return setError (E_FAIL,
1412 tr ("Invalid machine state: %d"), mData->mMachineState);
1413
1414 /* see if the device on the controller is already busy */
1415 for (HDData::HDAttachmentList::const_iterator it = mHDData->mHDAttachments.begin();
1416 it != mHDData->mHDAttachments.end(); ++ it)
1417 {
1418 ComObjPtr <HardDiskAttachment> hda = *it;
1419 if (hda->controller() == aCtl && hda->deviceNumber() == aDev)
1420 {
1421 ComObjPtr <HardDisk> hd = hda->hardDisk();
1422 AutoLock hdLock (hd);
1423 return setError (E_FAIL,
1424 tr ("Hard disk '%ls' is already attached to device slot %d "
1425 "on controller %d"),
1426 hd->toString().raw(), aDev, aCtl);
1427 }
1428 }
1429
1430 /* find a hard disk by UUID */
1431 ComObjPtr <HardDisk> hd;
1432 HRESULT rc = mParent->getHardDisk (id, hd);
1433 if (FAILED (rc))
1434 return rc;
1435
1436 AutoLock hdLock (hd);
1437
1438 if (hd->isDifferencing())
1439 return setError (E_FAIL,
1440 tr ("Cannot attach the differencing hard disk '%ls'"),
1441 hd->toString().raw());
1442
1443 bool dirty = false;
1444
1445 switch (hd->type())
1446 {
1447 case HardDiskType_ImmutableHardDisk:
1448 {
1449 Assert (hd->machineId().isEmpty());
1450 /*
1451 * increase readers to protect from unregistration
1452 * until rollback()/commit() is done
1453 */
1454 hd->addReader();
1455 // LogTraceMsg (("A: %ls proteced\n", hd->toString().raw()));
1456 dirty = true;
1457 break;
1458 }
1459 case HardDiskType_WritethroughHardDisk:
1460 {
1461 Assert (hd->children().size() == 0);
1462 Assert (hd->snapshotId().isEmpty());
1463 /* fall through */
1464 }
1465 case HardDiskType_NormalHardDisk:
1466 {
1467 if (hd->machineId().isEmpty())
1468 {
1469 /* attach directly */
1470 hd->setMachineId (mData->mUuid);
1471 // LogTraceMsg (("A: %ls associated with %Vuuid\n",
1472 // hd->toString().raw(), mData->mUuid.raw()));
1473 dirty = true;
1474 }
1475 else
1476 {
1477 /* determine what the hard disk is already attached to */
1478 if (hd->snapshotId().isEmpty())
1479 {
1480 /* attached to some VM in its current state */
1481 if (hd->machineId() == mData->mUuid)
1482 {
1483 /*
1484 * attached to us, either in the backed up list of the
1485 * attachments or in the current one; the former is ok
1486 * (reattachment takes place within the same
1487 * "transaction") the latter is an error so check for it
1488 */
1489 for (HDData::HDAttachmentList::const_iterator it =
1490 mHDData->mHDAttachments.begin();
1491 it != mHDData->mHDAttachments.end(); ++ it)
1492 {
1493 if ((*it)->hardDisk().equalsTo (hd))
1494 {
1495 return setError (E_FAIL,
1496 tr ("Normal/Writethrough hard disk '%ls' is "
1497 "currently attached to device slot %d "
1498 "on controller %d of this machine"),
1499 hd->toString().raw(),
1500 (*it)->deviceNumber(), (*it)->controller());
1501 }
1502 }
1503 /*
1504 * dirty = false to indicate we didn't set machineId
1505 * and prevent it from being reset in DetachHardDisk()
1506 */
1507 // LogTraceMsg (("A: %ls found in old\n", hd->toString().raw()));
1508 }
1509 else
1510 {
1511 /* attached to other VM */
1512 return setError (E_FAIL,
1513 tr ("Normal/Writethrough hard disk '%ls' is "
1514 "currently attached to a machine with "
1515 "UUID {%Vuuid}"),
1516 hd->toString().raw(), hd->machineId().raw());
1517 }
1518 }
1519 else
1520 {
1521 /*
1522 * here we go when the HardDiskType_NormalHardDisk
1523 * is attached to some VM (probably to this one, too)
1524 * at some particular snapshot, so we can create a diff
1525 * based on it
1526 */
1527 Assert (!hd->machineId().isEmpty());
1528 /*
1529 * increase readers to protect from unregistration
1530 * until rollback()/commit() is done
1531 */
1532 hd->addReader();
1533 // LogTraceMsg (("A: %ls proteced\n", hd->toString().raw()));
1534 dirty = true;
1535 }
1536 }
1537
1538 break;
1539 }
1540 }
1541
1542 ComObjPtr <HardDiskAttachment> attachment;
1543 attachment.createObject();
1544 attachment->init (hd, aCtl, aDev, dirty);
1545
1546 mHDData.backup();
1547 mHDData->mHDAttachments.push_back (attachment);
1548 // LogTraceMsg (("A: %ls attached\n", hd->toString().raw()));
1549
1550 /* note: diff images are actually created only in commit() */
1551
1552 return S_OK;
1553}
1554
1555STDMETHODIMP Machine::GetHardDisk (DiskControllerType_T aCtl,
1556 LONG aDev, IHardDisk **aHardDisk)
1557{
1558 if (aCtl == DiskControllerType_InvalidController ||
1559 aDev < 0 || aDev > 1)
1560 return E_INVALIDARG;
1561
1562 AutoCaller autoCaller (this);
1563 CheckComRCReturnRC (autoCaller.rc());
1564
1565 AutoReaderLock alock (this);
1566
1567 *aHardDisk = NULL;
1568
1569 for (HDData::HDAttachmentList::const_iterator it = mHDData->mHDAttachments.begin();
1570 it != mHDData->mHDAttachments.end(); ++ it)
1571 {
1572 ComObjPtr <HardDiskAttachment> hda = *it;
1573 if (hda->controller() == aCtl && hda->deviceNumber() == aDev)
1574 {
1575 hda->hardDisk().queryInterfaceTo (aHardDisk);
1576 return S_OK;
1577 }
1578 }
1579
1580 return setError (E_INVALIDARG,
1581 tr ("No hard disk attached to device slot %d on controller %d"),
1582 aDev, aCtl);
1583}
1584
1585STDMETHODIMP Machine::DetachHardDisk (DiskControllerType_T aCtl, LONG aDev)
1586{
1587 if (aCtl == DiskControllerType_InvalidController ||
1588 aDev < 0 || aDev > 1)
1589 return E_INVALIDARG;
1590
1591 AutoCaller autoCaller (this);
1592 CheckComRCReturnRC (autoCaller.rc());
1593
1594 AutoLock alock (this);
1595
1596 CHECK_SETTER();
1597
1598 AssertReturn (mData->mMachineState != MachineState_Saved, E_FAIL);
1599
1600 if (mData->mMachineState >= MachineState_Running)
1601 return setError (E_FAIL,
1602 tr ("Invalid machine state: %d"), mData->mMachineState);
1603
1604 for (HDData::HDAttachmentList::iterator it = mHDData->mHDAttachments.begin();
1605 it != mHDData->mHDAttachments.end(); ++ it)
1606 {
1607 ComObjPtr <HardDiskAttachment> hda = *it;
1608 if (hda->controller() == aCtl && hda->deviceNumber() == aDev)
1609 {
1610 ComObjPtr <HardDisk> hd = hda->hardDisk();
1611 AutoLock hdLock (hd);
1612
1613 ComAssertRet (hd->children().size() == 0 &&
1614 hd->machineId() == mData->mUuid, E_FAIL);
1615
1616 if (hda->isDirty())
1617 {
1618 switch (hd->type())
1619 {
1620 case HardDiskType_ImmutableHardDisk:
1621 {
1622 /* decrease readers increased in AttachHardDisk() */
1623 hd->releaseReader();
1624 // LogTraceMsg (("D: %ls released\n", hd->toString().raw()));
1625 break;
1626 }
1627 case HardDiskType_WritethroughHardDisk:
1628 {
1629 /* deassociate from this machine */
1630 hd->setMachineId (Guid());
1631 // LogTraceMsg (("D: %ls deassociated\n", hd->toString().raw()));
1632 break;
1633 }
1634 case HardDiskType_NormalHardDisk:
1635 {
1636 if (hd->snapshotId().isEmpty())
1637 {
1638 /* deassociate from this machine */
1639 hd->setMachineId (Guid());
1640 // LogTraceMsg (("D: %ls deassociated\n", hd->toString().raw()));
1641 }
1642 else
1643 {
1644 /* decrease readers increased in AttachHardDisk() */
1645 hd->releaseReader();
1646 // LogTraceMsg (("%ls released\n", hd->toString().raw()));
1647 }
1648
1649 break;
1650 }
1651 }
1652 }
1653
1654 mHDData.backup();
1655 /*
1656 * we cannot use erase (it) below because backup() above will create
1657 * a copy of the list and make this copy active, but the iterator
1658 * still refers to the original and is not valid for a copy
1659 */
1660 mHDData->mHDAttachments.remove (hda);
1661 // LogTraceMsg (("D: %ls detached\n", hd->toString().raw()));
1662
1663 /*
1664 * note: Non-dirty hard disks are actually deassociated
1665 * and diff images are deleted only in commit()
1666 */
1667
1668 return S_OK;
1669 }
1670 }
1671
1672 return setError (E_INVALIDARG,
1673 tr ("No hard disk attached to device slot %d on controller %d"),
1674 aDev, aCtl);
1675}
1676
1677STDMETHODIMP Machine::GetNetworkAdapter (ULONG slot, INetworkAdapter **adapter)
1678{
1679 if (!adapter)
1680 return E_POINTER;
1681 if (slot >= ELEMENTS (mNetworkAdapters))
1682 return setError (E_INVALIDARG, tr ("Invalid slot number: %d"), slot);
1683
1684 AutoCaller autoCaller (this);
1685 CheckComRCReturnRC (autoCaller.rc());
1686
1687 AutoReaderLock alock (this);
1688
1689 mNetworkAdapters [slot].queryInterfaceTo (adapter);
1690
1691 return S_OK;
1692}
1693
1694/**
1695 * Returns the extra data key name following the given key. If the key
1696 * is not found, an error is returned. If NULL is supplied, the first
1697 * key will be returned. If key is the last item, NULL will be returned.
1698 *
1699 * @returns COM status code
1700 * @param key extra data key name
1701 * @param nextKey name of the key following "key". NULL if "key" is the last.
1702 * @param nextValue value of the key following "key". Optional parameter.
1703 */
1704STDMETHODIMP Machine::GetNextExtraDataKey(INPTR BSTR key, BSTR *nextKey, BSTR *nextValue)
1705{
1706 if (!nextKey)
1707 return E_POINTER;
1708
1709 AutoCaller autoCaller (this);
1710 CheckComRCReturnRC (autoCaller.rc());
1711
1712 AutoReaderLock alock (this);
1713
1714 /* start with nothing found */
1715 *nextKey = NULL;
1716
1717 /*
1718 * if we're ready and isConfigLocked() is FALSE then it means
1719 * that no config file exists yet, so return shortly
1720 */
1721 if (!isConfigLocked())
1722 return S_OK;
1723
1724 HRESULT rc = S_OK;
1725
1726 /* load the config file */
1727 CFGHANDLE configLoader = 0;
1728 rc = openConfigLoader (&configLoader);
1729 if (FAILED (rc))
1730 return E_FAIL;
1731
1732 CFGNODE machineNode;
1733 CFGNODE extraDataNode;
1734
1735 /* navigate to the right position */
1736 if (VBOX_SUCCESS(CFGLDRGetNode(configLoader, "VirtualBox/Machine", 0, &machineNode)) &&
1737 VBOX_SUCCESS(CFGLDRGetChildNode(machineNode, "ExtraData", 0, &extraDataNode)))
1738 {
1739 /* check if it exists */
1740 bool found = false;
1741 unsigned count;
1742 CFGNODE extraDataItemNode;
1743 CFGLDRCountChildren(extraDataNode, "ExtraDataItem", &count);
1744 for (unsigned i = 0; (i < count) && (found == false); i++)
1745 {
1746 Bstr name;
1747 CFGLDRGetChildNode(extraDataNode, "ExtraDataItem", i, &extraDataItemNode);
1748 CFGLDRQueryBSTR(extraDataItemNode, "name", name.asOutParam());
1749
1750 /* if we're supposed to return the first one */
1751 if (key == NULL)
1752 {
1753 name.cloneTo(nextKey);
1754 if (nextValue)
1755 CFGLDRQueryBSTR(extraDataItemNode, "value", nextValue);
1756 found = true;
1757 }
1758 /* did we find the key we're looking for? */
1759 else if (name == key)
1760 {
1761 found = true;
1762 /* is there another item? */
1763 if (i + 1 < count)
1764 {
1765 CFGLDRGetChildNode(extraDataNode, "ExtraDataItem", i + 1, &extraDataItemNode);
1766 CFGLDRQueryBSTR(extraDataItemNode, "name", name.asOutParam());
1767 name.cloneTo(nextKey);
1768 if (nextValue)
1769 CFGLDRQueryBSTR(extraDataItemNode, "value", nextValue);
1770 found = true;
1771 }
1772 else
1773 {
1774 /* it's the last one */
1775 *nextKey = NULL;
1776 }
1777 }
1778 CFGLDRReleaseNode(extraDataItemNode);
1779 }
1780
1781 /* if we haven't found the key, it's an error */
1782 if (!found)
1783 rc = setError(E_FAIL, tr("Could not find extra data key"));
1784
1785 CFGLDRReleaseNode(extraDataNode);
1786 CFGLDRReleaseNode(machineNode);
1787 }
1788
1789 closeConfigLoader (configLoader, false /* aSaveBeforeClose */);
1790
1791 return rc;
1792}
1793
1794/**
1795 * Returns associated extra data from the configuration. If the key does
1796 * not exist, NULL will be stored in the output pointer.
1797 *
1798 * @returns COM status code
1799 * @param key extra data key
1800 * @param value address of result pointer
1801 */
1802STDMETHODIMP Machine::GetExtraData(INPTR BSTR key, BSTR *value)
1803{
1804 if (!key)
1805 return E_INVALIDARG;
1806 if (!value)
1807 return E_POINTER;
1808
1809 AutoCaller autoCaller (this);
1810 CheckComRCReturnRC (autoCaller.rc());
1811
1812 AutoReaderLock alock (this);
1813
1814 /* start with nothing found */
1815 *value = NULL;
1816
1817 /*
1818 * if we're ready and isConfigLocked() is FALSE then it means
1819 * that no config file exists yet, so return shortly
1820 */
1821 if (!isConfigLocked())
1822 return S_OK;
1823
1824 HRESULT rc = S_OK;
1825
1826 /* load the config file */
1827 CFGHANDLE configLoader = 0;
1828 rc = openConfigLoader (&configLoader);
1829 if (FAILED (rc))
1830 return E_FAIL;
1831
1832 CFGNODE machineNode;
1833 CFGNODE extraDataNode;
1834
1835 /* navigate to the right position */
1836 if (VBOX_SUCCESS(CFGLDRGetNode(configLoader, "VirtualBox/Machine", 0, &machineNode)) &&
1837 VBOX_SUCCESS(CFGLDRGetChildNode(machineNode, "ExtraData", 0, &extraDataNode)))
1838 {
1839 /* check if it exists */
1840 bool found = false;
1841 unsigned count;
1842 CFGNODE extraDataItemNode;
1843 CFGLDRCountChildren(extraDataNode, "ExtraDataItem", &count);
1844 for (unsigned i = 0; (i < count) && (found == false); i++)
1845 {
1846 Bstr name;
1847 CFGLDRGetChildNode(extraDataNode, "ExtraDataItem", i, &extraDataItemNode);
1848 CFGLDRQueryBSTR(extraDataItemNode, "name", name.asOutParam());
1849 if (name == key)
1850 {
1851 found = true;
1852 CFGLDRQueryBSTR(extraDataItemNode, "value", value);
1853 }
1854 CFGLDRReleaseNode(extraDataItemNode);
1855 }
1856
1857 CFGLDRReleaseNode(extraDataNode);
1858 CFGLDRReleaseNode(machineNode);
1859 }
1860
1861 rc = closeConfigLoader (configLoader, false /* aSaveBeforeClose */);
1862
1863 return rc;
1864}
1865
1866/**
1867 * Stores associated extra data in the configuration. If the data value is NULL
1868 * then the corresponding extra data item is deleted. This method can be called
1869 * outside a session and therefore belongs to the non protected machine data.
1870 *
1871 * @param key extra data key
1872 * @param value extra data value
1873 *
1874 * @note Locks mParent for reading + this object for writing.
1875 */
1876STDMETHODIMP Machine::SetExtraData (INPTR BSTR key, INPTR BSTR value)
1877{
1878 if (!key)
1879 return E_INVALIDARG;
1880
1881 AutoCaller autoCaller (this);
1882 CheckComRCReturnRC (autoCaller.rc());
1883
1884 /* VirtualBox::onExtraDataCanChange() needs mParent lock */
1885 AutoMultiLock <2> alock (mParent->rlock(), this->wlock());
1886
1887 if (mType == IsSnapshotMachine)
1888 CHECK_SETTER();
1889
1890 bool changed = false;
1891 HRESULT rc = S_OK;
1892
1893 /*
1894 * if we're ready and isConfigLocked() is FALSE then it means
1895 * that no config file exists yet, so call saveSettings() to create one
1896 */
1897 if (!isConfigLocked())
1898 {
1899 rc = saveSettings (false /* aMarkCurStateAsModified */);
1900 if (FAILED (rc))
1901 return rc;
1902 }
1903
1904 /* load the config file */
1905 CFGHANDLE configLoader = 0;
1906 rc = openConfigLoader (&configLoader);
1907 if (FAILED (rc))
1908 return rc;
1909
1910 CFGNODE machineNode = 0;
1911 CFGNODE extraDataNode = 0;
1912
1913 int vrc = CFGLDRGetNode (configLoader, "VirtualBox/Machine", 0, &machineNode);
1914 if (VBOX_FAILURE (vrc))
1915 vrc = CFGLDRCreateNode (configLoader, "VirtualBox/Machine", &machineNode);
1916
1917 vrc = CFGLDRGetChildNode (machineNode, "ExtraData", 0, &extraDataNode);
1918 if (VBOX_FAILURE (vrc) && value)
1919 vrc = CFGLDRCreateChildNode (machineNode, "ExtraData", &extraDataNode);
1920
1921 if (extraDataNode)
1922 {
1923 CFGNODE extraDataItemNode = 0;
1924 Bstr oldVal;
1925
1926 unsigned count;
1927 CFGLDRCountChildren (extraDataNode, "ExtraDataItem", &count);
1928
1929 for (unsigned i = 0; i < count; i++)
1930 {
1931 CFGLDRGetChildNode (extraDataNode, "ExtraDataItem", i, &extraDataItemNode);
1932 Bstr name;
1933 CFGLDRQueryBSTR (extraDataItemNode, "name", name.asOutParam());
1934 if (name == key)
1935 {
1936 CFGLDRQueryBSTR (extraDataItemNode, "value", oldVal.asOutParam());
1937 break;
1938 }
1939 CFGLDRReleaseNode (extraDataItemNode);
1940 extraDataItemNode = 0;
1941 }
1942
1943 /*
1944 * When no key is found, oldVal is null
1945 * Note:
1946 * 1. when oldVal is null, |oldVal == (BSTR) NULL| is true
1947 * 2. we cannot do |oldVal != value| because it will compare
1948 * BSTR pointers instead of strings (due to type conversion ops)
1949 */
1950 changed = !(oldVal == value);
1951
1952 if (changed)
1953 {
1954 /* ask for permission from all listeners */
1955 if (!mParent->onExtraDataCanChange (mData->mUuid, key, value))
1956 {
1957 LogWarningFunc (("Someone vetoed! Change refused!\n"));
1958 rc = setError (E_ACCESSDENIED,
1959 tr ("Could not set extra data because someone refused "
1960 "the requested change of '%ls' to '%ls'"), key, value);
1961 }
1962 else
1963 {
1964 if (value)
1965 {
1966 if (!extraDataItemNode)
1967 {
1968 /* create a new item */
1969 CFGLDRAppendChildNode (extraDataNode, "ExtraDataItem",
1970 &extraDataItemNode);
1971 CFGLDRSetBSTR (extraDataItemNode, "name", key);
1972 }
1973 CFGLDRSetBSTR (extraDataItemNode, "value", value);
1974 }
1975 else
1976 {
1977 /* an old value does for sure exist here */
1978 CFGLDRDeleteNode (extraDataItemNode);
1979 extraDataItemNode = 0;
1980 }
1981 }
1982 }
1983
1984 if (extraDataItemNode)
1985 CFGLDRReleaseNode (extraDataItemNode);
1986
1987 CFGLDRReleaseNode (extraDataNode);
1988 }
1989
1990 CFGLDRReleaseNode (machineNode);
1991
1992 if (SUCCEEDED (rc) && changed)
1993 rc = closeConfigLoader (configLoader, true /* aSaveBeforeClose */);
1994 else
1995 closeConfigLoader (configLoader, false /* aSaveBeforeClose */);
1996
1997 /* fire an event */
1998 if (SUCCEEDED (rc) && changed)
1999 {
2000 mParent->onExtraDataChange (mData->mUuid, key, value);
2001 }
2002
2003 return rc;
2004}
2005
2006STDMETHODIMP Machine::SaveSettings()
2007{
2008 AutoCaller autoCaller (this);
2009 CheckComRCReturnRC (autoCaller.rc());
2010
2011 /* Under some circumstancies, saveSettings() needs mParent lock */
2012 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
2013
2014 CHECK_SETTER();
2015
2016 /* the settings file path may never be null */
2017 ComAssertRet (mData->mConfigFileFull, E_FAIL);
2018
2019 /* save all VM data excluding snapshots */
2020 return saveSettings();
2021}
2022
2023STDMETHODIMP Machine::DiscardSettings()
2024{
2025 AutoCaller autoCaller (this);
2026 CheckComRCReturnRC (autoCaller.rc());
2027
2028 AutoLock alock (this);
2029
2030 CHECK_SETTER();
2031
2032 /*
2033 * during this rollback, the session will be notified if data has
2034 * been actually changed
2035 */
2036 rollback (true /* aNotify */);
2037
2038 return S_OK;
2039}
2040
2041STDMETHODIMP Machine::DeleteSettings()
2042{
2043 AutoCaller autoCaller (this);
2044 CheckComRCReturnRC (autoCaller.rc());
2045
2046 AutoLock alock (this);
2047
2048 CHECK_SETTER();
2049
2050 if (mData->mRegistered)
2051 return setError (E_FAIL,
2052 tr ("Cannot delete settings of a registered machine"));
2053
2054 /* delete the settings only when the file actually exists */
2055 if (isConfigLocked())
2056 {
2057 unlockConfig();
2058 int vrc = RTFileDelete (Utf8Str (mData->mConfigFileFull));
2059 if (VBOX_FAILURE (vrc))
2060 return setError (E_FAIL,
2061 tr ("Could not delete the settings file '%ls' (%Vrc)"),
2062 mData->mConfigFileFull.raw(), vrc);
2063
2064 /* delete the Logs folder, nothing important should be left
2065 * there (we don't check for errors because the user might have
2066 * some private files there that we don't want to delete) */
2067 Utf8Str logFolder;
2068 getLogFolder (logFolder);
2069 Assert (!logFolder.isEmpty());
2070 if (RTDirExists (logFolder))
2071 {
2072 /* delete all VBox.log[.N] files from the Logs folder
2073 * (this must be in sync with the rotation logic in
2074 * Console::powerUpThread()) */
2075 Utf8Str log = Utf8StrFmt ("%s/VBox.log", logFolder.raw());
2076 RTFileDelete (log);
2077 for (int i = 3; i >= 0; i--)
2078 {
2079 log = Utf8StrFmt ("%s/VBox.log.%d", logFolder.raw(), i);
2080 RTFileDelete (log);
2081 }
2082
2083 RTDirRemove (logFolder);
2084 }
2085
2086 /* delete the Snapshots folder, nothing important should be left
2087 * there (we don't check for errors because the user might have
2088 * some private files there that we don't want to delete) */
2089 Utf8Str snapshotFolder = mUserData->mSnapshotFolderFull;
2090 Assert (!snapshotFolder.isEmpty());
2091 if (RTDirExists (snapshotFolder))
2092 RTDirRemove (snapshotFolder);
2093
2094 /* delete the directory that contains the settings file, but only
2095 * if it matches the VM name (i.e. a structure created by default in
2096 * openConfigLoader()) */
2097 {
2098 Utf8Str settingsDir;
2099 if (isInOwnDir (&settingsDir))
2100 RTDirRemove (settingsDir);
2101 }
2102 }
2103
2104 return S_OK;
2105}
2106
2107STDMETHODIMP Machine::GetSnapshot (INPTR GUIDPARAM aId, ISnapshot **aSnapshot)
2108{
2109 if (!aSnapshot)
2110 return E_POINTER;
2111
2112 AutoCaller autoCaller (this);
2113 CheckComRCReturnRC (autoCaller.rc());
2114
2115 AutoReaderLock alock (this);
2116
2117 Guid id = aId;
2118 ComObjPtr <Snapshot> snapshot;
2119
2120 HRESULT rc = findSnapshot (id, snapshot, true /* aSetError */);
2121 snapshot.queryInterfaceTo (aSnapshot);
2122
2123 return rc;
2124}
2125
2126STDMETHODIMP Machine::FindSnapshot (INPTR BSTR aName, ISnapshot **aSnapshot)
2127{
2128 if (!aName)
2129 return E_INVALIDARG;
2130 if (!aSnapshot)
2131 return E_POINTER;
2132
2133 AutoCaller autoCaller (this);
2134 CheckComRCReturnRC (autoCaller.rc());
2135
2136 AutoReaderLock alock (this);
2137
2138 ComObjPtr <Snapshot> snapshot;
2139
2140 HRESULT rc = findSnapshot (aName, snapshot, true /* aSetError */);
2141 snapshot.queryInterfaceTo (aSnapshot);
2142
2143 return rc;
2144}
2145
2146STDMETHODIMP Machine::SetCurrentSnapshot (INPTR GUIDPARAM aId)
2147{
2148 /// @todo (dmik) don't forget to set
2149 // mData->mCurrentStateModified to FALSE
2150
2151 return setError (E_NOTIMPL, "Not implemented");
2152}
2153
2154STDMETHODIMP
2155Machine::CreateSharedFolder (INPTR BSTR aName, INPTR BSTR aHostPath)
2156{
2157 if (!aName || !aHostPath)
2158 return E_INVALIDARG;
2159
2160 AutoCaller autoCaller (this);
2161 CheckComRCReturnRC (autoCaller.rc());
2162
2163 AutoLock alock (this);
2164
2165 CHECK_SETTER();
2166
2167 /// @todo (dmik) check global shared folders when they are done
2168
2169 ComObjPtr <SharedFolder> sharedFolder;
2170 HRESULT rc = findSharedFolder (aName, sharedFolder, false /* aSetError */);
2171 if (SUCCEEDED (rc))
2172 return setError (E_FAIL,
2173 tr ("Shared folder named '%ls' already exists"), aName);
2174
2175 sharedFolder.createObject();
2176 rc = sharedFolder->init (machine(), aName, aHostPath);
2177 if (FAILED (rc))
2178 return rc;
2179
2180 BOOL accessible = FALSE;
2181 rc = sharedFolder->COMGETTER(Accessible) (&accessible);
2182 if (FAILED (rc))
2183 return rc;
2184
2185 if (!accessible)
2186 return setError (E_FAIL,
2187 tr ("Shared folder path '%ls' is not accessible"), aHostPath);
2188
2189 mHWData.backup();
2190 mHWData->mSharedFolders.push_back (sharedFolder);
2191
2192 return S_OK;
2193}
2194
2195STDMETHODIMP Machine::RemoveSharedFolder (INPTR BSTR aName)
2196{
2197 if (!aName)
2198 return E_INVALIDARG;
2199
2200 AutoCaller autoCaller (this);
2201 CheckComRCReturnRC (autoCaller.rc());
2202
2203 AutoReaderLock alock (this);
2204
2205 CHECK_SETTER();
2206
2207 ComObjPtr <SharedFolder> sharedFolder;
2208 HRESULT rc = findSharedFolder (aName, sharedFolder, true /* aSetError */);
2209 if (FAILED (rc))
2210 return rc;
2211
2212 mHWData.backup();
2213 mHWData->mSharedFolders.remove (sharedFolder);
2214
2215 return S_OK;
2216}
2217
2218// public methods for internal purposes
2219/////////////////////////////////////////////////////////////////////////////
2220
2221/**
2222 * Returns the session machine object associated with the this machine.
2223 * The returned session machine is null if no direct session is currently open.
2224 *
2225 * @Note locks this object for reading.
2226 */
2227ComObjPtr <SessionMachine> Machine::sessionMachine()
2228{
2229 ComObjPtr <SessionMachine> sm;
2230
2231 AutoCaller autoCaller (this);
2232 /* the machine may be inaccessible, so don't assert below */
2233 if (FAILED (autoCaller.rc()))
2234 return sm;
2235
2236 AutoReaderLock alock (this);
2237
2238 sm = mData->mSession.mMachine;
2239 Assert (!sm.isNull() ||
2240 mData->mSession.mState != SessionState_SessionOpen);
2241
2242 return sm;
2243}
2244
2245/**
2246 * Calculates the absolute path of the given path taking the directory of
2247 * the machine settings file as the current directory.
2248 *
2249 * @param aPath path to calculate the absolute path for
2250 * @param aResult where to put the result (used only on success,
2251 * so can be the same Utf8Str instance as passed as \a aPath)
2252 * @return VirtualBox result
2253 *
2254 * @note Locks this object for reading.
2255 */
2256int Machine::calculateFullPath (const char *aPath, Utf8Str &aResult)
2257{
2258 AutoCaller autoCaller (this);
2259 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
2260
2261 AutoReaderLock alock (this);
2262
2263 AssertReturn (!mData->mConfigFileFull.isNull(), VERR_GENERAL_FAILURE);
2264
2265 Utf8Str settingsDir = mData->mConfigFileFull;
2266
2267 RTPathStripFilename (settingsDir.mutableRaw());
2268 char folder [RTPATH_MAX];
2269 int vrc = RTPathAbsEx (settingsDir, aPath,
2270 folder, sizeof (folder));
2271 if (VBOX_SUCCESS (vrc))
2272 aResult = folder;
2273
2274 return vrc;
2275}
2276
2277/**
2278 * Tries to calculate the relative path of the given absolute path using the
2279 * directory of the machine settings file as the base directory.
2280 *
2281 * @param aPath absolute path to calculate the relative path for
2282 * @param aResult where to put the result (used only when it's possible to
2283 * make a relative path from the given absolute path;
2284 * otherwise left untouched)
2285 *
2286 * @note Locks this object for reading.
2287 */
2288void Machine::calculateRelativePath (const char *aPath, Utf8Str &aResult)
2289{
2290 AutoCaller autoCaller (this);
2291 AssertComRCReturn (autoCaller.rc(), (void) 0);
2292
2293 AutoReaderLock alock (this);
2294
2295 AssertReturnVoid (!mData->mConfigFileFull.isNull());
2296
2297 Utf8Str settingsDir = mData->mConfigFileFull;
2298
2299 RTPathStripFilename (settingsDir.mutableRaw());
2300 if (RTPathStartsWith (aPath, settingsDir))
2301 {
2302 /* when assigning, we create a separate Utf8Str instance because both
2303 * aPath and aResult can point to the same memory location when this
2304 * func is called (if we just do aResult = aPath, aResult will be freed
2305 * first, and since its the same as aPath, an attempt to copy garbage
2306 * will be made. */
2307 aResult = Utf8Str (aPath + settingsDir.length() + 1);
2308 }
2309}
2310
2311/**
2312 * Returns the full path to the machine's log folder in the
2313 * \a aLogFolder argument.
2314 */
2315void Machine::getLogFolder (Utf8Str &aLogFolder)
2316{
2317 AutoCaller autoCaller (this);
2318 AssertComRCReturn (autoCaller.rc(), (void) 0);
2319
2320 AutoReaderLock alock (this);
2321
2322 Utf8Str settingsDir;
2323 if (isInOwnDir (&settingsDir))
2324 {
2325 /* Log folder is <Machines>/<VM_Name>/Logs */
2326 aLogFolder = Utf8StrFmt ("%s%cLogs", settingsDir.raw(), RTPATH_DELIMITER);
2327 }
2328 else
2329 {
2330 /* Log folder is <Machines>/<VM_SnapshotFolder>/Logs */
2331 Assert (!mUserData->mSnapshotFolderFull.isEmpty());
2332 aLogFolder = Utf8StrFmt ("%ls%cLogs", mUserData->mSnapshotFolderFull.raw(),
2333 RTPATH_DELIMITER);
2334 }
2335}
2336
2337/**
2338 * @note Locks mParent and this object for writing,
2339 * calls the client process (outside the lock).
2340 */
2341HRESULT Machine::openSession (IInternalSessionControl *aControl)
2342{
2343 LogFlowThisFuncEnter();
2344
2345 AssertReturn (aControl, E_FAIL);
2346
2347 AutoCaller autoCaller (this);
2348 CheckComRCReturnRC (autoCaller.rc());
2349
2350 /* We need VirtualBox lock because of Progress::notifyComplete() */
2351 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
2352
2353 if (!mData->mRegistered)
2354 return setError (E_UNEXPECTED,
2355 tr ("The machine '%ls' is not registered"), mUserData->mName.raw());
2356
2357 LogFlowThisFunc (("mSession.mState=%d\n", mData->mSession.mState));
2358
2359 if (mData->mSession.mState == SessionState_SessionOpen ||
2360 mData->mSession.mState == SessionState_SessionClosing)
2361 return setError (E_ACCESSDENIED,
2362 tr ("A session for the machine '%ls' is currently open "
2363 "(or being closed)"),
2364 mUserData->mName.raw());
2365
2366 /* may not be Running */
2367 AssertReturn (mData->mMachineState < MachineState_Running, E_FAIL);
2368
2369 if (mData->mSession.mState == SessionState_SessionSpawning)
2370 {
2371 /*
2372 * this machine awaits for a spawning session to be opened,
2373 * so reject any other open attempts from processes other than
2374 * one started by #openRemoteSession().
2375 */
2376
2377 RTPROCESS pid = NIL_RTPROCESS; AssertCompile (sizeof (ULONG) == sizeof (RTPROCESS));
2378 aControl->GetPID ((ULONG *)&pid);
2379
2380 LogFlowThisFunc (("mSession.mPid=%d(0x%x)\n",
2381 mData->mSession.mPid, mData->mSession.mPid));
2382 LogFlowThisFunc (("session.pid=%d(0x%x)\n", pid, pid));
2383
2384 if (mData->mSession.mPid != pid)
2385 return setError (E_ACCESSDENIED,
2386 tr ("An unexpected process (PID=0x%08X) has tried to open a direct "
2387 "session with the machine named '%ls', while only a process "
2388 "started by OpenRemoteSession (PID=0x%08X) is allowed"),
2389 pid, mUserData->mName.raw(), mData->mSession.mPid);
2390 }
2391
2392 /* create a SessionMachine object */
2393 ComObjPtr <SessionMachine> sessionMachine;
2394 sessionMachine.createObject();
2395 HRESULT rc = sessionMachine->init (this);
2396 AssertComRC (rc);
2397
2398 if (SUCCEEDED (rc))
2399 {
2400 /*
2401 * Set the session state to Spawning to protect against subsequent
2402 * attempts to open a session and to unregister the machine after
2403 * we leave the lock.
2404 */
2405 SessionState_T origState = mData->mSession.mState;
2406 mData->mSession.mState = SessionState_SessionSpawning;
2407
2408 /*
2409 * Leave the lock before calling the client process -- it will call
2410 * Machine/SessionMachine methods. Leaving the lock here is quite safe
2411 * because the state is Spawning, so that openRemotesession() and
2412 * openExistingSession() calls will fail. This method, called before we
2413 * enter the lock again, will fail because of the wrong PID.
2414 *
2415 * Note that mData->mSession.mRemoteControls accessed outside
2416 * the lock may not be modified when state is Spawning, so it's safe.
2417 */
2418 alock.leave();
2419
2420 LogFlowThisFunc (("Calling AssignMachine()...\n"));
2421 rc = aControl->AssignMachine (sessionMachine);
2422 LogFlowThisFunc (("AssignMachine() returned %08X\n", rc));
2423
2424 /* The failure may w/o any error info (from RPC), so provide one */
2425 if (FAILED (rc))
2426 setError (rc,
2427 tr ("Failed to assign the machine to the session"));
2428
2429 if (SUCCEEDED (rc) && origState == SessionState_SessionSpawning)
2430 {
2431 /* complete the remote session initialization */
2432
2433 /* get the console from the direct session */
2434 ComPtr <IConsole> console;
2435 rc = aControl->GetRemoteConsole (console.asOutParam());
2436 ComAssertComRC (rc);
2437
2438 if (SUCCEEDED (rc) && !console)
2439 {
2440 ComAssert (!!console);
2441 rc = E_FAIL;
2442 }
2443
2444 /* assign machine & console to the remote sesion */
2445 if (SUCCEEDED (rc))
2446 {
2447 /*
2448 * after openRemoteSession(), the first and the only
2449 * entry in remoteControls is that remote session
2450 */
2451 LogFlowThisFunc (("Calling AssignRemoteMachine()...\n"));
2452 rc = mData->mSession.mRemoteControls.front()->
2453 AssignRemoteMachine (sessionMachine, console);
2454 LogFlowThisFunc (("AssignRemoteMachine() returned %08X\n", rc));
2455
2456 /* The failure may w/o any error info (from RPC), so provide one */
2457 if (FAILED (rc))
2458 setError (rc,
2459 tr ("Failed to assign the machine to the remote session"));
2460 }
2461
2462 if (FAILED (rc))
2463 aControl->Uninitialize();
2464 }
2465
2466 /* enter the lock again */
2467 alock.enter();
2468
2469 /* Restore the session state */
2470 mData->mSession.mState = origState;
2471 }
2472
2473 /* finalize spawning amyway (this is why we don't return on errors above) */
2474 if (mData->mSession.mState == SessionState_SessionSpawning)
2475 {
2476 /* Note that the progress object is finalized later */
2477
2478 /* We don't reset mSession.mPid here because it is necessary for
2479 * SessionMachine::uninit() to reap the child process later. */
2480
2481 if (FAILED (rc))
2482 {
2483 /* Remove the remote control from the list on failure
2484 * and reset session state to Closed. */
2485 mData->mSession.mRemoteControls.clear();
2486 mData->mSession.mState = SessionState_SessionClosed;
2487 }
2488 }
2489
2490 if (SUCCEEDED (rc))
2491 {
2492 /* memorize the direct session control */
2493 mData->mSession.mDirectControl = aControl;
2494 mData->mSession.mState = SessionState_SessionOpen;
2495 /* associate the SessionMachine with this Machine */
2496 mData->mSession.mMachine = sessionMachine;
2497 }
2498
2499 if (mData->mSession.mProgress)
2500 {
2501 /* finalize the progress after setting the state, for consistency */
2502 mData->mSession.mProgress->notifyComplete (rc);
2503 mData->mSession.mProgress.setNull();
2504 }
2505
2506 /* uninitialize the created session machine on failure */
2507 if (FAILED (rc))
2508 sessionMachine->uninit();
2509
2510 LogFlowThisFunc (("rc=%08X\n", rc));
2511 LogFlowThisFuncLeave();
2512 return rc;
2513}
2514
2515/**
2516 * @note Locks this object for writing, calls the client process
2517 * (inside the lock).
2518 */
2519HRESULT Machine::openRemoteSession (IInternalSessionControl *aControl,
2520 INPTR BSTR aType, Progress *aProgress)
2521{
2522 LogFlowThisFuncEnter();
2523
2524 AssertReturn (aControl, E_FAIL);
2525 AssertReturn (aProgress, E_FAIL);
2526
2527 AutoCaller autoCaller (this);
2528 CheckComRCReturnRC (autoCaller.rc());
2529
2530 AutoLock alock (this);
2531
2532 if (!mData->mRegistered)
2533 return setError (E_UNEXPECTED,
2534 tr ("The machine '%ls' is not registered"), mUserData->mName.raw());
2535
2536 LogFlowThisFunc (("mSession.mState=%d\n", mData->mSession.mState));
2537
2538 if (mData->mSession.mState == SessionState_SessionOpen ||
2539 mData->mSession.mState == SessionState_SessionSpawning ||
2540 mData->mSession.mState == SessionState_SessionClosing)
2541 return setError (E_ACCESSDENIED,
2542 tr ("A session for the machine '%ls' is currently open "
2543 "(or being opened or closed)"),
2544 mUserData->mName.raw());
2545
2546 /* may not be Running */
2547 AssertReturn (mData->mMachineState < MachineState_Running, E_FAIL);
2548
2549 /* get the path to the executable */
2550 char path [RTPATH_MAX];
2551 RTPathProgram (path, RTPATH_MAX);
2552 size_t sz = strlen (path);
2553 path [sz++] = RTPATH_DELIMITER;
2554 path [sz] = 0;
2555 char *cmd = path + sz;
2556 sz = RTPATH_MAX - sz;
2557
2558 int vrc = VINF_SUCCESS;
2559 RTPROCESS pid = NIL_RTPROCESS;
2560
2561 Bstr type (aType);
2562 if (type == "gui")
2563 {
2564 const char VirtualBox_exe[] = "VirtualBox" HOSTSUFF_EXE;
2565 Assert (sz >= sizeof (VirtualBox_exe));
2566 strcpy (cmd, VirtualBox_exe);
2567
2568 Utf8Str idStr = mData->mUuid.toString();
2569 const char * args[] = {path, "-startvm", idStr, 0 };
2570 vrc = RTProcCreate (path, args, NULL, 0, &pid);
2571 }
2572 else
2573#ifdef VBOX_VRDP
2574 if (type == "vrdp")
2575 {
2576 const char VBoxVRDP_exe[] = "VBoxVRDP" HOSTSUFF_EXE;
2577 Assert (sz >= sizeof (VBoxVRDP_exe));
2578 strcpy (cmd, VBoxVRDP_exe);
2579
2580 Utf8Str idStr = mData->mUuid.toString();
2581 const char * args[] = {path, "-startvm", idStr, 0 };
2582 vrc = RTProcCreate (path, args, NULL, 0, &pid);
2583 }
2584 else
2585#endif /* VBOX_VRDP */
2586 if (type == "capture")
2587 {
2588 const char VBoxVRDP_exe[] = "VBoxVRDP" HOSTSUFF_EXE;
2589 Assert (sz >= sizeof (VBoxVRDP_exe));
2590 strcpy (cmd, VBoxVRDP_exe);
2591
2592 Utf8Str idStr = mData->mUuid.toString();
2593 const char * args[] = {path, "-startvm", idStr, "-capture", 0 };
2594 vrc = RTProcCreate (path, args, NULL, 0, &pid);
2595 }
2596 else
2597 {
2598 return setError (E_INVALIDARG,
2599 tr ("Invalid session type: '%ls'"), aType);
2600 }
2601
2602 if (VBOX_FAILURE (vrc))
2603 return setError (E_FAIL,
2604 tr ("Could not launch a process for the machine '%ls' (%Vrc)"),
2605 mUserData->mName.raw(), vrc);
2606
2607 LogFlowThisFunc (("launched.pid=%d(0x%x)\n", pid, pid));
2608
2609 /*
2610 * Note that we don't leave the lock here before calling the client,
2611 * because it doesn't need to call us back if called with a NULL argument.
2612 * Leaving the lock herer is dangerous because we didn't prepare the
2613 * launch data yet, but the client we've just started may happen to be
2614 * too fast and call openSession() that will fail (because of PID, etc.),
2615 * so that the Machine will never get out of the Spawning session state.
2616 */
2617
2618 /* inform the session that it will be a remote one */
2619 LogFlowThisFunc (("Calling AssignMachine (NULL)...\n"));
2620 HRESULT rc = aControl->AssignMachine (NULL);
2621 LogFlowThisFunc (("AssignMachine (NULL) returned %08X\n", rc));
2622
2623 if (FAILED (rc))
2624 {
2625 /* restore the session state */
2626 mData->mSession.mState = SessionState_SessionClosed;
2627 /* The failure may w/o any error info (from RPC), so provide one */
2628 return setError (rc,
2629 tr ("Failed to assign the machine to the session"));
2630 }
2631
2632 /* attach launch data to the machine */
2633 Assert (mData->mSession.mPid == NIL_RTPROCESS);
2634 mData->mSession.mRemoteControls.push_back (aControl);
2635 mData->mSession.mProgress = aProgress;
2636 mData->mSession.mPid = pid;
2637 mData->mSession.mState = SessionState_SessionSpawning;
2638
2639 LogFlowThisFuncLeave();
2640 return S_OK;
2641}
2642
2643/**
2644 * @note Locks this object for writing, calls the client process
2645 * (outside the lock).
2646 */
2647HRESULT Machine::openExistingSession (IInternalSessionControl *aControl)
2648{
2649 LogFlowThisFuncEnter();
2650
2651 AssertReturn (aControl, E_FAIL);
2652
2653 AutoCaller autoCaller (this);
2654 CheckComRCReturnRC (autoCaller.rc());
2655
2656 AutoLock alock (this);
2657
2658 if (!mData->mRegistered)
2659 return setError (E_UNEXPECTED,
2660 tr ("The machine '%ls' is not registered"), mUserData->mName.raw());
2661
2662 LogFlowThisFunc (("mSession.state=%d\n", mData->mSession.mState));
2663
2664 if (mData->mSession.mState != SessionState_SessionOpen)
2665 return setError (E_ACCESSDENIED,
2666 tr ("The machine '%ls' does not have an open session"),
2667 mUserData->mName.raw());
2668
2669 ComAssertRet (!mData->mSession.mDirectControl.isNull(), E_FAIL);
2670
2671 /*
2672 * Get the console from the direct session (note that we don't leave the
2673 * lock here because GetRemoteConsole must not call us back).
2674 */
2675 ComPtr <IConsole> console;
2676 HRESULT rc = mData->mSession.mDirectControl->
2677 GetRemoteConsole (console.asOutParam());
2678 if (FAILED (rc))
2679 {
2680 /* The failure may w/o any error info (from RPC), so provide one */
2681 return setError (rc,
2682 tr ("Failed to get a console object from the direct session"));
2683 }
2684
2685 ComAssertRet (!console.isNull(), E_FAIL);
2686
2687 ComObjPtr <SessionMachine> sessionMachine = mData->mSession.mMachine;
2688 AssertReturn (!sessionMachine.isNull(), E_FAIL);
2689
2690 /*
2691 * Leave the lock before calling the client process. It's safe here
2692 * since the only thing to do after we get the lock again is to add
2693 * the remote control to the list (which doesn't directly influence
2694 * anything).
2695 */
2696 alock.leave();
2697
2698 /* attach the remote session to the machine */
2699 LogFlowThisFunc (("Calling AssignRemoteMachine()...\n"));
2700 rc = aControl->AssignRemoteMachine (sessionMachine, console);
2701 LogFlowThisFunc (("AssignRemoteMachine() returned %08X\n", rc));
2702
2703 /* The failure may w/o any error info (from RPC), so provide one */
2704 if (FAILED (rc))
2705 return setError (rc,
2706 tr ("Failed to assign the machine to the session"));
2707
2708 alock.enter();
2709
2710 /* need to revalidate the state after entering the lock again */
2711 if (mData->mSession.mState != SessionState_SessionOpen)
2712 {
2713 aControl->Uninitialize();
2714
2715 return setError (E_ACCESSDENIED,
2716 tr ("The machine '%ls' does not have an open session"),
2717 mUserData->mName.raw());
2718 }
2719
2720 /* store the control in the list */
2721 mData->mSession.mRemoteControls.push_back (aControl);
2722
2723 LogFlowThisFuncLeave();
2724 return S_OK;
2725}
2726
2727/**
2728 * Checks that the registered flag of the machine can be set according to
2729 * the argument and sets it. On success, commits and saves all settings.
2730 *
2731 * @note When this machine is inaccessible, the only valid value for \a
2732 * aRegistered is FALSE (i.e. unregister the machine) because unregistered
2733 * inaccessible machines are not currently supported. Note that unregistering
2734 * an inaccessible machine will \b uninitialize this machine object. Therefore,
2735 * the caller must make sure there are no active Machine::addCaller() calls
2736 * on the current thread because this will block Machine::uninit().
2737 *
2738 * @note Locks this object and children for writing!
2739 */
2740HRESULT Machine::trySetRegistered (BOOL aRegistered)
2741{
2742 AutoLimitedCaller autoCaller (this);
2743 AssertComRCReturnRC (autoCaller.rc());
2744
2745 AutoLock alock (this);
2746
2747 ComAssertRet (mData->mRegistered != aRegistered, E_FAIL);
2748
2749 if (!mData->mAccessible)
2750 {
2751 /* A special case: the machine is not accessible. */
2752
2753 /* inaccessible machines can only be unregistered */
2754 AssertReturn (!aRegistered, E_FAIL);
2755
2756 /* Uninitialize ourselves here because currently there may be no
2757 * unregistered that are inaccessible (this state combination is not
2758 * supported). Note releasing the caller and leaving the lock before
2759 * calling uninit() */
2760
2761 alock.leave();
2762 autoCaller.release();
2763
2764 uninit();
2765
2766 return S_OK;
2767 }
2768
2769 AssertReturn (autoCaller.state() == Ready, E_FAIL);
2770
2771 if (aRegistered)
2772 {
2773 if (mData->mRegistered)
2774 return setError (E_FAIL,
2775 tr ("The machine '%ls' with UUID {%s} is already registered"),
2776 mUserData->mName.raw(),
2777 mData->mUuid.toString().raw());
2778 }
2779 else
2780 {
2781 if (mData->mMachineState == MachineState_Saved)
2782 return setError (E_FAIL,
2783 tr ("Cannot unregister the machine '%ls' because it "
2784 "is in the Saved state"),
2785 mUserData->mName.raw());
2786
2787 size_t snapshotCount = 0;
2788 if (mData->mFirstSnapshot)
2789 snapshotCount = mData->mFirstSnapshot->descendantCount() + 1;
2790 if (snapshotCount)
2791 return setError (E_FAIL,
2792 tr ("Cannot unregister the machine '%ls' because it "
2793 "has %d snapshots"),
2794 mUserData->mName.raw(), snapshotCount);
2795
2796 if (mData->mSession.mState != SessionState_SessionClosed)
2797 return setError (E_FAIL,
2798 tr ("Cannot unregister the machine '%ls' because it has an "
2799 "open session"),
2800 mUserData->mName.raw());
2801
2802 if (mHDData->mHDAttachments.size() != 0)
2803 return setError (E_FAIL,
2804 tr ("Cannot unregister the machine '%ls' because it "
2805 "has %d hard disks attached"),
2806 mUserData->mName.raw(), mHDData->mHDAttachments.size());
2807 }
2808
2809 /* Ensure the settings are saved. If we are going to be registered and
2810 * isConfigLocked() is FALSE then it means that no config file exists yet,
2811 * so create it. */
2812 if (isModified() || (aRegistered && !isConfigLocked()))
2813 {
2814 HRESULT rc = saveSettings();
2815 CheckComRCReturnRC (rc);
2816 }
2817
2818 mData->mRegistered = aRegistered;
2819
2820 /* inform the USB proxy about all attached/detached USB filters */
2821 mUSBController->onMachineRegistered (aRegistered);
2822
2823 return S_OK;
2824}
2825
2826// protected methods
2827/////////////////////////////////////////////////////////////////////////////
2828
2829/**
2830 * Helper to uninitialize all associated child objects
2831 * and to free all data structures.
2832 *
2833 * This method must be called as a part of the object's uninitialization
2834 * procedure (usually done in the uninit() method).
2835 *
2836 * @note Must be called only from uninit().
2837 */
2838void Machine::uninitDataAndChildObjects()
2839{
2840 AutoCaller autoCaller (this);
2841 AssertComRCReturn (autoCaller.rc(), (void) 0);
2842 AssertComRCReturn (autoCaller.state( ) == InUninit, (void) 0);
2843
2844 /* tell all our child objects we've been uninitialized */
2845
2846 /*
2847 * uninit all children using addDependentChild()/removeDependentChild()
2848 * in their init()/uninit() methods
2849 */
2850 uninitDependentChildren();
2851
2852 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
2853 {
2854 if (mNetworkAdapters [slot])
2855 {
2856 mNetworkAdapters [slot]->uninit();
2857 unconst (mNetworkAdapters [slot]).setNull();
2858 }
2859 }
2860
2861 if (mUSBController)
2862 {
2863 mUSBController->uninit();
2864 unconst (mUSBController).setNull();
2865 }
2866
2867 if (mAudioAdapter)
2868 {
2869 mAudioAdapter->uninit();
2870 unconst (mAudioAdapter).setNull();
2871 }
2872
2873 if (mFloppyDrive)
2874 {
2875 mFloppyDrive->uninit();
2876 unconst (mFloppyDrive).setNull();
2877 }
2878
2879 if (mDVDDrive)
2880 {
2881 mDVDDrive->uninit();
2882 unconst (mDVDDrive).setNull();
2883 }
2884
2885#ifdef VBOX_VRDP
2886 if (mVRDPServer)
2887 {
2888 mVRDPServer->uninit();
2889 unconst (mVRDPServer).setNull();
2890 }
2891#endif
2892
2893 if (mBIOSSettings)
2894 {
2895 mBIOSSettings->uninit();
2896 unconst (mBIOSSettings).setNull();
2897 }
2898
2899 /* free data structures */
2900 mSSData.free();
2901 mHDData.free();
2902 mHWData.free();
2903 mUserData.free();
2904 mData.free();
2905}
2906
2907/**
2908 * Helper to change the machine state.
2909 *
2910 * @note Locks this object for writing.
2911 */
2912HRESULT Machine::setMachineState (MachineState_T aMachineState)
2913{
2914 LogFlowThisFuncEnter();
2915 LogFlowThisFunc (("aMachineState=%d\n", aMachineState));
2916
2917 AutoCaller autoCaller (this);
2918 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
2919
2920 AutoLock alock (this);
2921
2922 if (mData->mMachineState != aMachineState)
2923 {
2924 mData->mMachineState = aMachineState;
2925
2926 RTTIMESPEC time;
2927 mData->mLastStateChange = RTTimeSpecGetMilli(RTTimeNow(&time));
2928
2929 mParent->onMachineStateChange (mData->mUuid, aMachineState);
2930 }
2931
2932 LogFlowThisFuncLeave();
2933 return S_OK;
2934}
2935
2936/**
2937 * Searches for a shared folder with the given logical name
2938 * in the collection of shared folders.
2939 *
2940 * @param aName logical name of the shared folder
2941 * @param aSharedFolder where to return the found object
2942 * @param aSetError whether to set the error info if the folder is
2943 * not found
2944 * @return
2945 * S_OK when found or E_INVALIDARG when not found
2946 *
2947 * @note
2948 * must be called from under the object's lock!
2949 */
2950HRESULT Machine::findSharedFolder (const BSTR aName,
2951 ComObjPtr <SharedFolder> &aSharedFolder,
2952 bool aSetError /* = false */)
2953{
2954 bool found = false;
2955 for (HWData::SharedFolderList::const_iterator it = mHWData->mSharedFolders.begin();
2956 !found && it != mHWData->mSharedFolders.end();
2957 ++ it)
2958 {
2959 AutoLock alock (*it);
2960 found = (*it)->name() == aName;
2961 if (found)
2962 aSharedFolder = *it;
2963 }
2964
2965 HRESULT rc = found ? S_OK : E_INVALIDARG;
2966
2967 if (aSetError && !found)
2968 setError (rc, tr ("Could not find a shared folder named '%ls'"), aName);
2969
2970 return rc;
2971}
2972
2973/**
2974 * Loads all the VM settings by walking down the <Machine> node.
2975 *
2976 * @param aRegistered true when the machine is being loaded on VirtualBox
2977 * startup
2978 *
2979 * @note This method is intended to be called only from init(), so it assumes
2980 * all machine data fields have appropriate default values when it is called.
2981 *
2982 * @note Doesn't lock any objects.
2983 */
2984HRESULT Machine::loadSettings (bool aRegistered)
2985{
2986 LogFlowThisFuncEnter();
2987 AssertReturn (mType == IsMachine, E_FAIL);
2988
2989 AutoCaller autoCaller (this);
2990 AssertReturn (autoCaller.state() == InInit, E_FAIL);
2991
2992 HRESULT rc = S_OK;
2993
2994 CFGHANDLE configLoader = NULL;
2995 char *loaderError = NULL;
2996 int vrc = CFGLDRLoad (&configLoader,
2997 Utf8Str (mData->mConfigFileFull), mData->mHandleCfgFile,
2998 XmlSchemaNS, true, cfgLdrEntityResolver,
2999 &loaderError);
3000 if (VBOX_FAILURE (vrc))
3001 {
3002 rc = setError (E_FAIL,
3003 tr ("Could not load the settings file '%ls' (%Vrc)%s%s"),
3004 mData->mConfigFileFull.raw(), vrc,
3005 loaderError ? ".\n" : "", loaderError ? loaderError : "");
3006
3007 if (loaderError)
3008 RTMemTmpFree (loaderError);
3009
3010 LogFlowThisFuncLeave();
3011 return rc;
3012 }
3013
3014 /*
3015 * When reading the XML, we assume it has been validated, so we don't
3016 * do any structural checks here, Just Assert() some things.
3017 */
3018
3019 CFGNODE machineNode = 0;
3020 CFGLDRGetNode (configLoader, "VirtualBox/Machine", 0, &machineNode);
3021
3022 do
3023 {
3024 ComAssertBreak (machineNode, rc = E_FAIL);
3025
3026 /* uuid (required) */
3027 Guid id;
3028 CFGLDRQueryUUID (machineNode, "uuid", id.ptr());
3029
3030 /* If the stored UUID is not empty, it means the registered machine
3031 * is being loaded. Compare the loaded UUID with the stored one taken
3032 * from the global registry. */
3033 if (!mData->mUuid.isEmpty())
3034 {
3035 if (mData->mUuid != id)
3036 {
3037 rc = setError (E_FAIL,
3038 tr ("Machine UUID {%Vuuid} in '%ls' doesn't match its "
3039 "UUID {%s} in the registry file '%ls'"),
3040 id.raw(), mData->mConfigFileFull.raw(),
3041 mData->mUuid.toString().raw(),
3042 mParent->settingsFileName().raw());
3043 break;
3044 }
3045 }
3046 else
3047 unconst (mData->mUuid) = id;
3048
3049 /* name (required) */
3050 CFGLDRQueryBSTR (machineNode, "name", mUserData->mName.asOutParam());
3051
3052 /* nameSync (optional, default is true) */
3053 {
3054 bool nameSync = true;
3055 CFGLDRQueryBool (machineNode, "nameSync", &nameSync);
3056 mUserData->mNameSync = nameSync;
3057 }
3058
3059 /* OSType (required) */
3060 {
3061 Bstr osTypeId;
3062 CFGLDRQueryBSTR (machineNode, "OSType", osTypeId.asOutParam());
3063
3064 /* look up the object in our list */
3065 ComPtr <IGuestOSType> guestOSType;
3066 rc = mParent->FindGuestOSType (osTypeId, guestOSType.asOutParam());
3067 if (FAILED (rc))
3068 break;
3069
3070 mUserData->mOSType = guestOSType;
3071 }
3072
3073 /* stateFile (optional) */
3074 {
3075 Bstr stateFilePath;
3076 CFGLDRQueryBSTR (machineNode, "stateFile", stateFilePath.asOutParam());
3077 if (stateFilePath)
3078 {
3079 Utf8Str stateFilePathFull = stateFilePath;
3080 int vrc = calculateFullPath (stateFilePathFull, stateFilePathFull);
3081 if (VBOX_FAILURE (vrc))
3082 {
3083 rc = setError (E_FAIL,
3084 tr ("Invalid saved state file path: '%ls' (%Vrc)"),
3085 stateFilePath.raw(), vrc);
3086 break;
3087 }
3088 mSSData->mStateFilePath = stateFilePathFull;
3089 }
3090 else
3091 mSSData->mStateFilePath.setNull();
3092 }
3093
3094 /*
3095 * currentSnapshot ID (optional)
3096 * Note that due to XML Schema constaraints this attribute, when present,
3097 * will guaranteedly refer to an existing snapshot definition in XML
3098 */
3099 Guid currentSnapshotId;
3100 CFGLDRQueryUUID (machineNode, "currentSnapshot", currentSnapshotId.ptr());
3101
3102 /* snapshotFolder (optional) */
3103 {
3104 Bstr folder;
3105 CFGLDRQueryBSTR (machineNode, "snapshotFolder", folder.asOutParam());
3106 rc = COMSETTER(SnapshotFolder) (folder);
3107 if (FAILED (rc))
3108 break;
3109 }
3110
3111 /* lastStateChange (optional, for compatiblity) */
3112 {
3113 int64_t lastStateChange = 0;
3114 CFGLDRQueryDateTime (machineNode, "lastStateChange", &lastStateChange);
3115 if (lastStateChange == 0)
3116 {
3117 /// @todo (dmik) until lastStateChange is the required attribute,
3118 // we simply set it to the current time if missing in the config
3119 RTTIMESPEC time;
3120 lastStateChange = RTTimeSpecGetMilli (RTTimeNow (&time));
3121 }
3122 mData->mLastStateChange = lastStateChange;
3123 }
3124
3125 /* aborted (optional) */
3126 bool aborted = false;
3127 CFGLDRQueryBool (machineNode, "aborted", &aborted);
3128
3129 /* currentStateModified (optional, default is true) */
3130 mData->mCurrentStateModified = TRUE;
3131 {
3132 bool val = true;
3133 CFGLDRQueryBool (machineNode, "currentStateModified", &val);
3134 mData->mCurrentStateModified = val;
3135 }
3136
3137 /*
3138 * note: all mUserData members must be assigned prior this point because
3139 * we need to commit changes in order to let mUserData be shared by all
3140 * snapshot machine instances.
3141 */
3142 mUserData.commitCopy();
3143
3144 /* Snapshot node (optional) */
3145 {
3146 CFGNODE snapshotNode = 0;
3147 CFGLDRGetChildNode (machineNode, "Snapshot", 0, &snapshotNode);
3148 if (snapshotNode)
3149 {
3150 /* read all snapshots recursively */
3151 rc = loadSnapshot (snapshotNode, currentSnapshotId, NULL);
3152 CFGLDRReleaseNode (snapshotNode);
3153 if (FAILED (rc))
3154 break;
3155 }
3156 }
3157
3158 /* Hardware node (required) */
3159 {
3160 CFGNODE hardwareNode = 0;
3161 CFGLDRGetChildNode (machineNode, "Hardware", 0, &hardwareNode);
3162 ComAssertBreak (hardwareNode, rc = E_FAIL);
3163 rc = loadHardware (hardwareNode);
3164 CFGLDRReleaseNode (hardwareNode);
3165 if (FAILED (rc))
3166 break;
3167 }
3168
3169 /* HardDiskAttachments node (required) */
3170 {
3171 CFGNODE hdasNode = 0;
3172 CFGLDRGetChildNode (machineNode, "HardDiskAttachments", 0, &hdasNode);
3173 ComAssertBreak (hdasNode, rc = E_FAIL);
3174
3175 rc = loadHardDisks (hdasNode, aRegistered);
3176 CFGLDRReleaseNode (hdasNode);
3177 if (FAILED (rc))
3178 break;
3179 }
3180
3181 /*
3182 * NOTE: the assignment below must be the last thing to do,
3183 * otherwise it will be not possible to change the settings
3184 * somewehere in the code above because all setters will be
3185 * blocked by CHECK_SETTER()
3186 */
3187
3188 /* set the machine state to Aborted or Saved when appropriate */
3189 if (aborted)
3190 {
3191 Assert (!mSSData->mStateFilePath);
3192 mSSData->mStateFilePath.setNull();
3193
3194 mData->mMachineState = MachineState_Aborted;
3195 }
3196 else if (mSSData->mStateFilePath)
3197 {
3198 mData->mMachineState = MachineState_Saved;
3199 }
3200 }
3201 while (0);
3202
3203 if (machineNode)
3204 CFGLDRReleaseNode (machineNode);
3205
3206 CFGLDRFree (configLoader);
3207
3208 LogFlowThisFuncLeave();
3209 return rc;
3210}
3211
3212/**
3213 * Recursively loads all snapshots starting from the given.
3214 *
3215 * @param aNode <Snapshot> node
3216 * @param aCurSnapshotId current snapshot ID from the settings file
3217 * @param aParentSnapshot parent snapshot
3218 */
3219HRESULT Machine::loadSnapshot (CFGNODE aNode, const Guid &aCurSnapshotId,
3220 Snapshot *aParentSnapshot)
3221{
3222 AssertReturn (aNode, E_INVALIDARG);
3223 AssertReturn (mType == IsMachine, E_FAIL);
3224
3225 // create a snapshot machine object
3226 ComObjPtr <SnapshotMachine> snapshotMachine;
3227 snapshotMachine.createObject();
3228
3229 HRESULT rc = S_OK;
3230
3231 Guid uuid; // required
3232 CFGLDRQueryUUID (aNode, "uuid", uuid.ptr());
3233
3234 Bstr stateFilePath; // optional
3235 CFGLDRQueryBSTR (aNode, "stateFile", stateFilePath.asOutParam());
3236 if (stateFilePath)
3237 {
3238 Utf8Str stateFilePathFull = stateFilePath;
3239 int vrc = calculateFullPath (stateFilePathFull, stateFilePathFull);
3240 if (VBOX_FAILURE (vrc))
3241 return setError (E_FAIL,
3242 tr ("Invalid saved state file path: '%ls' (%Vrc)"),
3243 stateFilePath.raw(), vrc);
3244
3245 stateFilePath = stateFilePathFull;
3246 }
3247
3248 do
3249 {
3250 // Hardware node (required)
3251 CFGNODE hardwareNode = 0;
3252 CFGLDRGetChildNode (aNode, "Hardware", 0, &hardwareNode);
3253 ComAssertBreak (hardwareNode, rc = E_FAIL);
3254
3255 do
3256 {
3257 // HardDiskAttachments node (required)
3258 CFGNODE hdasNode = 0;
3259 CFGLDRGetChildNode (aNode, "HardDiskAttachments", 0, &hdasNode);
3260 ComAssertBreak (hdasNode, rc = E_FAIL);
3261
3262 // initialize the snapshot machine
3263 rc = snapshotMachine->init (this, hardwareNode, hdasNode,
3264 uuid, stateFilePath);
3265
3266 CFGLDRReleaseNode (hdasNode);
3267 }
3268 while (0);
3269
3270 CFGLDRReleaseNode (hardwareNode);
3271 }
3272 while (0);
3273
3274 if (FAILED (rc))
3275 return rc;
3276
3277 // create a snapshot object
3278 ComObjPtr <Snapshot> snapshot;
3279 snapshot.createObject();
3280
3281 {
3282 Bstr name; // required
3283 CFGLDRQueryBSTR (aNode, "name", name.asOutParam());
3284
3285 LONG64 timeStamp = 0; // required
3286 CFGLDRQueryDateTime (aNode, "timeStamp", &timeStamp);
3287
3288 Bstr description; // optional
3289 {
3290 CFGNODE descNode = 0;
3291 CFGLDRGetChildNode (aNode, "Description", 0, &descNode);
3292 CFGLDRQueryBSTR (descNode, NULL, description.asOutParam());
3293 CFGLDRReleaseNode (descNode);
3294 }
3295
3296 // initialize the snapshot
3297 rc = snapshot->init (uuid, name, description, timeStamp,
3298 snapshotMachine, aParentSnapshot);
3299 if (FAILED (rc))
3300 return rc;
3301 }
3302
3303 // memorize the first snapshot if necessary
3304 if (!mData->mFirstSnapshot)
3305 mData->mFirstSnapshot = snapshot;
3306
3307 // memorize the current snapshot when appropriate
3308 if (!mData->mCurrentSnapshot && snapshot->data().mId == aCurSnapshotId)
3309 mData->mCurrentSnapshot = snapshot;
3310
3311 // Snapshots node (optional)
3312 {
3313 CFGNODE snapshotsNode = 0;
3314 CFGLDRGetChildNode (aNode, "Snapshots", 0, &snapshotsNode);
3315 if (snapshotsNode)
3316 {
3317 unsigned cbDisks = 0;
3318 CFGLDRCountChildren (snapshotsNode, "Snapshot", &cbDisks);
3319 for (unsigned i = 0; i < cbDisks && SUCCEEDED (rc); i++)
3320 {
3321 CFGNODE snapshotNode;
3322 CFGLDRGetChildNode (snapshotsNode, "Snapshot", i, &snapshotNode);
3323 ComAssertBreak (snapshotNode, rc = E_FAIL);
3324
3325 rc = loadSnapshot (snapshotNode, aCurSnapshotId, snapshot);
3326
3327 CFGLDRReleaseNode (snapshotNode);
3328 }
3329
3330 CFGLDRReleaseNode (snapshotsNode);
3331 }
3332 }
3333
3334 return rc;
3335}
3336
3337/**
3338 * @param aNode <Hardware> node
3339 */
3340HRESULT Machine::loadHardware (CFGNODE aNode)
3341{
3342 AssertReturn (aNode, E_INVALIDARG);
3343 AssertReturn (mType == IsMachine || mType == IsSnapshotMachine, E_FAIL);
3344
3345 /* CPU node (currently not required) */
3346 {
3347 /* default value in case the node is not there */
3348 mHWData->mHWVirtExEnabled = TriStateBool_Default;
3349
3350 CFGNODE cpuNode = 0;
3351 CFGLDRGetChildNode (aNode, "CPU", 0, &cpuNode);
3352 if (cpuNode)
3353 {
3354 CFGNODE hwVirtExNode = 0;
3355 CFGLDRGetChildNode (cpuNode, "HardwareVirtEx", 0, &hwVirtExNode);
3356 if (hwVirtExNode)
3357 {
3358 Bstr hwVirtExEnabled;
3359 CFGLDRQueryBSTR (hwVirtExNode, "enabled", hwVirtExEnabled.asOutParam());
3360 if (hwVirtExEnabled == L"false")
3361 mHWData->mHWVirtExEnabled = TriStateBool_False;
3362 else if (hwVirtExEnabled == L"true")
3363 mHWData->mHWVirtExEnabled = TriStateBool_True;
3364 else
3365 mHWData->mHWVirtExEnabled = TriStateBool_Default;
3366 CFGLDRReleaseNode (hwVirtExNode);
3367 }
3368 CFGLDRReleaseNode (cpuNode);
3369 }
3370 }
3371
3372 /* Memory node (required) */
3373 {
3374 CFGNODE memoryNode = 0;
3375 CFGLDRGetChildNode (aNode, "Memory", 0, &memoryNode);
3376 ComAssertRet (memoryNode, E_FAIL);
3377
3378 uint32_t RAMSize;
3379 CFGLDRQueryUInt32 (memoryNode, "RAMSize", &RAMSize);
3380 mHWData->mMemorySize = RAMSize;
3381 CFGLDRReleaseNode (memoryNode);
3382 }
3383
3384 /* Boot node (required) */
3385 {
3386 /* reset all boot order positions to NoDevice */
3387 for (size_t i = 0; i < ELEMENTS (mHWData->mBootOrder); i++)
3388 mHWData->mBootOrder [i] = DeviceType_NoDevice;
3389
3390 CFGNODE bootNode = 0;
3391 CFGLDRGetChildNode (aNode, "Boot", 0, &bootNode);
3392 ComAssertRet (bootNode, E_FAIL);
3393
3394 HRESULT rc = S_OK;
3395
3396 unsigned cOrder;
3397 CFGLDRCountChildren (bootNode, "Order", &cOrder);
3398 for (unsigned i = 0; i < cOrder; i++)
3399 {
3400 CFGNODE orderNode = 0;
3401 CFGLDRGetChildNode (bootNode, "Order", i, &orderNode);
3402 ComAssertBreak (orderNode, rc = E_FAIL);
3403
3404 /* position (required) */
3405 /* position unicity is guaranteed by XML Schema */
3406 uint32_t position = 0;
3407 CFGLDRQueryUInt32 (orderNode, "position", &position);
3408 -- position;
3409 Assert (position < ELEMENTS (mHWData->mBootOrder));
3410
3411 /* device (required) */
3412 Bstr device;
3413 CFGLDRQueryBSTR (orderNode, "device", device.asOutParam());
3414 if (device == L"None")
3415 mHWData->mBootOrder [position] = DeviceType_NoDevice;
3416 else if (device == L"Floppy")
3417 mHWData->mBootOrder [position] = DeviceType_FloppyDevice;
3418 else if (device == L"DVD")
3419 mHWData->mBootOrder [position] = DeviceType_DVDDevice;
3420 else if (device == L"HardDisk")
3421 mHWData->mBootOrder [position] = DeviceType_HardDiskDevice;
3422 else if (device == L"Network")
3423 mHWData->mBootOrder [position] = DeviceType_NetworkDevice;
3424 else
3425 ComAssertMsgFailed (("Invalid device: %ls\n", device.raw()));
3426
3427 CFGLDRReleaseNode (orderNode);
3428 }
3429
3430 CFGLDRReleaseNode (bootNode);
3431 if (FAILED (rc))
3432 return rc;
3433 }
3434
3435 /* Display node (required) */
3436 {
3437 CFGNODE displayNode = 0;
3438 CFGLDRGetChildNode (aNode, "Display", 0, &displayNode);
3439 ComAssertRet (displayNode, E_FAIL);
3440
3441 uint32_t VRAMSize;
3442 CFGLDRQueryUInt32 (displayNode, "VRAMSize", &VRAMSize);
3443 mHWData->mVRAMSize = VRAMSize;
3444 CFGLDRReleaseNode (displayNode);
3445 }
3446
3447#ifdef VBOX_VRDP
3448 /* RemoteDisplay node (optional) */
3449 /// @todo (dmik) move the code to VRDPServer
3450 /// @todo r=sunlover: moved. dmik, please review.
3451 {
3452 CFGNODE remoteDisplayNode = 0;
3453 CFGLDRGetChildNode (aNode, "RemoteDisplay", 0, &remoteDisplayNode);
3454 if (remoteDisplayNode)
3455 {
3456 mVRDPServer->loadConfig (remoteDisplayNode);
3457 CFGLDRReleaseNode (remoteDisplayNode);
3458 }
3459 }
3460#endif
3461
3462 /* BIOS node (required) */
3463 {
3464 CFGNODE biosNode = 0;
3465 CFGLDRGetChildNode (aNode, "BIOS", 0, &biosNode);
3466 ComAssertRet (biosNode, E_FAIL);
3467
3468 HRESULT rc = S_OK;
3469
3470 do
3471 {
3472 /* ACPI */
3473 {
3474 CFGNODE acpiNode = 0;
3475 CFGLDRGetChildNode (biosNode, "ACPI", 0, &acpiNode);
3476 ComAssertBreak (acpiNode, rc = E_FAIL);
3477
3478 bool enabled;
3479 CFGLDRQueryBool (acpiNode, "enabled", &enabled);
3480 mBIOSSettings->COMSETTER(ACPIEnabled)(enabled);
3481 CFGLDRReleaseNode (acpiNode);
3482 }
3483
3484 /* IOAPIC */
3485 {
3486 CFGNODE ioapicNode = 0;
3487 CFGLDRGetChildNode (biosNode, "IOAPIC", 0, &ioapicNode);
3488 if (ioapicNode)
3489 {
3490 bool enabled;
3491 CFGLDRQueryBool (ioapicNode, "enabled", &enabled);
3492 mBIOSSettings->COMSETTER(IOAPICEnabled)(enabled);
3493 CFGLDRReleaseNode (ioapicNode);
3494 }
3495 }
3496
3497 /* Logo (optional) */
3498 {
3499 CFGNODE logoNode = 0;
3500 CFGLDRGetChildNode (biosNode, "Logo", 0, &logoNode);
3501 if (logoNode)
3502 {
3503 bool enabled = false;
3504 CFGLDRQueryBool (logoNode, "fadeIn", &enabled);
3505 mBIOSSettings->COMSETTER(LogoFadeIn)(enabled);
3506 CFGLDRQueryBool (logoNode, "fadeOut", &enabled);
3507 mBIOSSettings->COMSETTER(LogoFadeOut)(enabled);
3508
3509 uint32_t BIOSLogoDisplayTime;
3510 CFGLDRQueryUInt32 (logoNode, "displayTime", &BIOSLogoDisplayTime);
3511 mBIOSSettings->COMSETTER(LogoDisplayTime)(BIOSLogoDisplayTime);
3512
3513 Bstr logoPath;
3514 CFGLDRQueryBSTR (logoNode, "imagePath", logoPath.asOutParam());
3515 mBIOSSettings->COMSETTER(LogoImagePath)(logoPath);
3516
3517 CFGLDRReleaseNode (logoNode);
3518 }
3519 }
3520
3521 /* boot menu (optional) */
3522 {
3523 CFGNODE bootMenuNode = 0;
3524 CFGLDRGetChildNode (biosNode, "BootMenu", 0, &bootMenuNode);
3525 if (bootMenuNode)
3526 {
3527 Bstr modeStr;
3528 BIOSBootMenuMode_T mode;
3529 CFGLDRQueryBSTR (bootMenuNode, "mode", modeStr.asOutParam());
3530 if (modeStr == L"disabled")
3531 mode = BIOSBootMenuMode_Disabled;
3532 else if (modeStr == L"menuonly")
3533 mode = BIOSBootMenuMode_MenuOnly;
3534 else
3535 mode = BIOSBootMenuMode_MessageAndMenu;
3536 mBIOSSettings->COMSETTER(BootMenuMode)(mode);
3537
3538 CFGLDRReleaseNode (bootMenuNode);
3539 }
3540 }
3541 }
3542 while (0);
3543
3544 CFGLDRReleaseNode (biosNode);
3545 if (FAILED (rc))
3546 return rc;
3547 }
3548
3549 /* DVD drive (contains either Image or HostDrive or nothing) */
3550 /// @todo (dmik) move the code to DVDDrive
3551 {
3552 HRESULT rc = S_OK;
3553
3554 CFGNODE dvdDriveNode = 0;
3555 CFGLDRGetChildNode (aNode, "DVDDrive", 0, &dvdDriveNode);
3556 ComAssertRet (dvdDriveNode, E_FAIL);
3557
3558 bool fPassthrough;
3559 CFGLDRQueryBool(dvdDriveNode, "passthrough", &fPassthrough);
3560 mDVDDrive->COMSETTER(Passthrough)(fPassthrough);
3561
3562 CFGNODE typeNode = 0;
3563
3564 do
3565 {
3566 CFGLDRGetChildNode (dvdDriveNode, "Image", 0, &typeNode);
3567 if (typeNode)
3568 {
3569 Guid uuid;
3570 CFGLDRQueryUUID (typeNode, "uuid", uuid.ptr());
3571 rc = mDVDDrive->MountImage (uuid);
3572 }
3573 else
3574 {
3575 CFGLDRGetChildNode (dvdDriveNode, "HostDrive", 0, &typeNode);
3576 if (typeNode)
3577 {
3578 Bstr src;
3579 CFGLDRQueryBSTR (typeNode, "src", src.asOutParam());
3580
3581 /* find the correspoding object */
3582 ComPtr <IHost> host;
3583 rc = mParent->COMGETTER(Host) (host.asOutParam());
3584 ComAssertComRCBreak (rc, rc = rc);
3585
3586 ComPtr <IHostDVDDriveCollection> coll;
3587 rc = host->COMGETTER(DVDDrives) (coll.asOutParam());
3588 ComAssertComRCBreak (rc, rc = rc);
3589
3590 ComPtr <IHostDVDDrive> drive;
3591 rc = coll->FindByName (src, drive.asOutParam());
3592 if (SUCCEEDED (rc))
3593 rc = mDVDDrive->CaptureHostDrive (drive);
3594 else if (rc == E_INVALIDARG)
3595 {
3596 /* the host DVD drive is not currently available. we
3597 * assume it will be available later and create an
3598 * extra object now */
3599 ComObjPtr <HostDVDDrive> hostDrive;
3600 hostDrive.createObject();
3601 rc = hostDrive->init (src);
3602 ComAssertComRCBreak (rc, rc = rc);
3603 rc = mDVDDrive->CaptureHostDrive (hostDrive);
3604 }
3605 else
3606 ComAssertComRCBreak (rc, rc = rc);
3607 }
3608 }
3609 }
3610 while (0);
3611
3612 if (typeNode)
3613 CFGLDRReleaseNode (typeNode);
3614 CFGLDRReleaseNode (dvdDriveNode);
3615
3616 if (FAILED (rc))
3617 return rc;
3618 }
3619
3620 /* Floppy drive (contains either Image or HostDrive or nothing) */
3621 /// @todo (dmik) move the code to FloppyDrive
3622 {
3623 HRESULT rc = S_OK;
3624
3625 CFGNODE driveNode = 0;
3626 CFGLDRGetChildNode (aNode, "FloppyDrive", 0, &driveNode);
3627 ComAssertRet (driveNode, E_FAIL);
3628
3629 BOOL fFloppyEnabled = TRUE;
3630 CFGLDRQueryBool (driveNode, "enabled", (bool*)&fFloppyEnabled);
3631 rc = mFloppyDrive->COMSETTER(Enabled)(fFloppyEnabled);
3632
3633 CFGNODE typeNode = 0;
3634 do
3635 {
3636 CFGLDRGetChildNode (driveNode, "Image", 0, &typeNode);
3637 if (typeNode)
3638 {
3639 Guid uuid;
3640 CFGLDRQueryUUID (typeNode, "uuid", uuid.ptr());
3641 rc = mFloppyDrive->MountImage (uuid);
3642 }
3643 else
3644 {
3645 CFGLDRGetChildNode (driveNode, "HostDrive", 0, &typeNode);
3646 if (typeNode)
3647 {
3648 Bstr src;
3649 CFGLDRQueryBSTR (typeNode, "src", src.asOutParam());
3650
3651 /* find the correspoding object */
3652 ComPtr <IHost> host;
3653 rc = mParent->COMGETTER(Host) (host.asOutParam());
3654 ComAssertComRCBreak (rc, rc = rc);
3655
3656 ComPtr <IHostFloppyDriveCollection> coll;
3657 rc = host->COMGETTER(FloppyDrives) (coll.asOutParam());
3658 ComAssertComRCBreak (rc, rc = rc);
3659
3660 ComPtr <IHostFloppyDrive> drive;
3661 rc = coll->FindByName (src, drive.asOutParam());
3662 if (SUCCEEDED (rc))
3663 rc = mFloppyDrive->CaptureHostDrive (drive);
3664 else if (rc == E_INVALIDARG)
3665 {
3666 /* the host Floppy drive is not currently available. we
3667 * assume it will be available later and create an
3668 * extra object now */
3669 ComObjPtr <HostFloppyDrive> hostDrive;
3670 hostDrive.createObject();
3671 rc = hostDrive->init (src);
3672 ComAssertComRCBreak (rc, rc = rc);
3673 rc = mFloppyDrive->CaptureHostDrive (hostDrive);
3674 }
3675 else
3676 ComAssertComRCBreak (rc, rc = rc);
3677 }
3678 }
3679 }
3680 while (0);
3681
3682 if (typeNode)
3683 CFGLDRReleaseNode (typeNode);
3684 CFGLDRReleaseNode (driveNode);
3685
3686 if (FAILED (rc))
3687 return rc;
3688 }
3689
3690 /* USB Controller */
3691 {
3692 HRESULT rc = mUSBController->loadSettings (aNode);
3693 if (FAILED (rc))
3694 return rc;
3695 }
3696
3697 /* Network node (required) */
3698 /// @todo (dmik) move the code to NetworkAdapter
3699 {
3700 /* we assume that all network adapters are initially disabled
3701 * and detached */
3702
3703 CFGNODE networkNode = 0;
3704 CFGLDRGetChildNode (aNode, "Network", 0, &networkNode);
3705 ComAssertRet (networkNode, E_FAIL);
3706
3707 HRESULT rc = S_OK;
3708
3709 unsigned cAdapters = 0;
3710 CFGLDRCountChildren (networkNode, "Adapter", &cAdapters);
3711 for (unsigned i = 0; i < cAdapters; i++)
3712 {
3713 CFGNODE adapterNode = 0;
3714 CFGLDRGetChildNode (networkNode, "Adapter", i, &adapterNode);
3715 ComAssertBreak (adapterNode, rc = E_FAIL);
3716
3717 /* slot number (required) */
3718 /* slot unicity is guaranteed by XML Schema */
3719 uint32_t slot = 0;
3720 CFGLDRQueryUInt32 (adapterNode, "slot", &slot);
3721 Assert (slot < ELEMENTS (mNetworkAdapters));
3722
3723 /* type */
3724 Bstr adapterType;
3725 CFGLDRQueryBSTR (adapterNode, "type", adapterType.asOutParam());
3726 ComAssertBreak (adapterType, rc = E_FAIL);
3727
3728 /* enabled (required) */
3729 bool enabled = false;
3730 CFGLDRQueryBool (adapterNode, "enabled", &enabled);
3731 /* MAC address (can be null) */
3732 Bstr macAddr;
3733 CFGLDRQueryBSTR (adapterNode, "MACAddress", macAddr.asOutParam());
3734 /* cable (required) */
3735 bool cableConnected;
3736 CFGLDRQueryBool (adapterNode, "cable", &cableConnected);
3737 /* tracing (defaults to false) */
3738 bool traceEnabled;
3739 CFGLDRQueryBool (adapterNode, "trace", &traceEnabled);
3740 Bstr traceFile;
3741 CFGLDRQueryBSTR (adapterNode, "tracefile", traceFile.asOutParam());
3742
3743 mNetworkAdapters [slot]->COMSETTER(Enabled) (enabled);
3744 mNetworkAdapters [slot]->COMSETTER(MACAddress) (macAddr);
3745 mNetworkAdapters [slot]->COMSETTER(CableConnected) (cableConnected);
3746 mNetworkAdapters [slot]->COMSETTER(TraceEnabled) (traceEnabled);
3747 mNetworkAdapters [slot]->COMSETTER(TraceFile) (traceFile);
3748
3749 if (adapterType.compare(Bstr("Am79C970A")) == 0)
3750 mNetworkAdapters [slot]->COMSETTER(AdapterType)(NetworkAdapterType_NetworkAdapterAm79C970A);
3751 else if (adapterType.compare(Bstr("Am79C973")) == 0)
3752 mNetworkAdapters [slot]->COMSETTER(AdapterType)(NetworkAdapterType_NetworkAdapterAm79C973);
3753 else
3754 ComAssertBreak (0, rc = E_FAIL);
3755
3756 CFGNODE attachmentNode = 0;
3757 if (CFGLDRGetChildNode (adapterNode, "NAT", 0, &attachmentNode), attachmentNode)
3758 {
3759 mNetworkAdapters [slot]->AttachToNAT();
3760 }
3761 else
3762 if (CFGLDRGetChildNode (adapterNode, "HostInterface", 0, &attachmentNode), attachmentNode)
3763 {
3764 /* Host Interface Networking */
3765 Bstr name;
3766 CFGLDRQueryBSTR (attachmentNode, "name", name.asOutParam());
3767#ifdef __WIN__
3768 /* @name can be empty on Win32, but not null */
3769 ComAssertBreak (!name.isNull(), rc = E_FAIL);
3770#endif
3771 mNetworkAdapters [slot]->COMSETTER(HostInterface) (name);
3772#ifdef VBOX_WITH_UNIXY_TAP_NETWORKING
3773 Bstr tapSetupApp;
3774 CFGLDRQueryBSTR (attachmentNode, "TAPSetup", tapSetupApp.asOutParam());
3775 Bstr tapTerminateApp;
3776 CFGLDRQueryBSTR (attachmentNode, "TAPTerminate", tapTerminateApp.asOutParam());
3777
3778 mNetworkAdapters [slot]->COMSETTER(TAPSetupApplication) (tapSetupApp);
3779 mNetworkAdapters [slot]->COMSETTER(TAPTerminateApplication) (tapTerminateApp);
3780#endif // VBOX_WITH_UNIXY_TAP_NETWORKING
3781 mNetworkAdapters [slot]->AttachToHostInterface();
3782 }
3783 else
3784 if (CFGLDRGetChildNode(adapterNode, "InternalNetwork", 0, &attachmentNode), attachmentNode)
3785 {
3786 /* Internal Networking */
3787 Bstr name;
3788 CFGLDRQueryBSTR (attachmentNode, "name", name.asOutParam());
3789 ComAssertBreak (!name.isNull(), rc = E_FAIL);
3790 mNetworkAdapters[slot]->AttachToInternalNetwork();
3791 mNetworkAdapters[slot]->COMSETTER(InternalNetwork) (name);
3792 }
3793 else
3794 {
3795 /* Adapter has no children */
3796 mNetworkAdapters [slot]->Detach();
3797 }
3798 if (attachmentNode)
3799 CFGLDRReleaseNode (attachmentNode);
3800
3801 CFGLDRReleaseNode (adapterNode);
3802 }
3803
3804 CFGLDRReleaseNode (networkNode);
3805 if (FAILED (rc))
3806 return rc;
3807 }
3808
3809 /* AudioAdapter node (required) */
3810 /// @todo (dmik) move the code to AudioAdapter
3811 {
3812 CFGNODE audioAdapterNode = 0;
3813 CFGLDRGetChildNode (aNode, "AudioAdapter", 0, &audioAdapterNode);
3814 ComAssertRet (audioAdapterNode, E_FAIL);
3815
3816 // is the adapter enabled?
3817 bool enabled = false;
3818 CFGLDRQueryBool (audioAdapterNode, "enabled", &enabled);
3819 mAudioAdapter->COMSETTER(Enabled) (enabled);
3820 // now check the audio driver
3821 Bstr driver;
3822 CFGLDRQueryBSTR (audioAdapterNode, "driver", driver.asOutParam());
3823 AudioDriverType_T audioDriver;
3824 audioDriver = AudioDriverType_NullAudioDriver;
3825 if (driver == L"null")
3826 ; // Null has been set above
3827#ifdef __WIN__
3828 else if (driver == L"winmm")
3829#ifdef VBOX_WITH_WINMM
3830 audioDriver = AudioDriverType_WINMMAudioDriver;
3831#else
3832 // fall back to dsound
3833 audioDriver = AudioDriverType_DSOUNDAudioDriver;
3834#endif
3835 else if (driver == L"dsound")
3836 audioDriver = AudioDriverType_DSOUNDAudioDriver;
3837#endif // __WIN__
3838#ifdef __LINUX__
3839 else if (driver == L"oss")
3840 audioDriver = AudioDriverType_OSSAudioDriver;
3841 else if (driver == L"alsa")
3842#ifdef VBOX_WITH_ALSA
3843 audioDriver = AudioDriverType_ALSAAudioDriver;
3844#else
3845 // fall back to OSS
3846 audioDriver = AudioDriverType_OSSAudioDriver;
3847#endif
3848#endif // __LINUX__
3849 else
3850 AssertMsgFailed (("Invalid driver: %ls\n", driver.raw()));
3851 mAudioAdapter->COMSETTER(AudioDriver) (audioDriver);
3852
3853 CFGLDRReleaseNode (audioAdapterNode);
3854 }
3855
3856 /* Shared folders (optional) */
3857 /// @todo (dmik) make required on next format change!
3858 do
3859 {
3860 CFGNODE sharedFoldersNode = 0;
3861 CFGLDRGetChildNode (aNode, "SharedFolders", 0, &sharedFoldersNode);
3862
3863 if (!sharedFoldersNode)
3864 break;
3865
3866 HRESULT rc = S_OK;
3867
3868 unsigned cFolders = 0;
3869 CFGLDRCountChildren (sharedFoldersNode, "SharedFolder", &cFolders);
3870
3871 for (unsigned i = 0; i < cFolders; i++)
3872 {
3873 CFGNODE folderNode = 0;
3874 CFGLDRGetChildNode (sharedFoldersNode, "SharedFolder", i, &folderNode);
3875 ComAssertBreak (folderNode, rc = E_FAIL);
3876
3877 // folder logical name (required)
3878 Bstr name;
3879 CFGLDRQueryBSTR (folderNode, "name", name.asOutParam());
3880
3881 // folder host path (required)
3882 Bstr hostPath;
3883 CFGLDRQueryBSTR (folderNode, "hostPath", hostPath.asOutParam());
3884
3885 rc = CreateSharedFolder (name, hostPath);
3886 if (FAILED (rc))
3887 break;
3888
3889 CFGLDRReleaseNode (folderNode);
3890 }
3891
3892 CFGLDRReleaseNode (sharedFoldersNode);
3893 if (FAILED (rc))
3894 return rc;
3895 }
3896 while (0);
3897
3898 /* Clipboard node (currently not required) */
3899 /// @todo (dmik) make required on next format change!
3900 {
3901 /* default value in case the node is not there */
3902 mHWData->mClipboardMode = ClipboardMode_ClipDisabled;
3903
3904 CFGNODE clipNode = 0;
3905 CFGLDRGetChildNode (aNode, "Clipboard", 0, &clipNode);
3906 if (clipNode)
3907 {
3908 Bstr mode;
3909 CFGLDRQueryBSTR (clipNode, "mode", mode.asOutParam());
3910 if (mode == L"Disabled")
3911 mHWData->mClipboardMode = ClipboardMode_ClipDisabled;
3912 else if (mode == L"HostToGuest")
3913 mHWData->mClipboardMode = ClipboardMode_ClipHostToGuest;
3914 else if (mode == L"GuestToHost")
3915 mHWData->mClipboardMode = ClipboardMode_ClipGuestToHost;
3916 else if (mode == L"Bidirectional")
3917 mHWData->mClipboardMode = ClipboardMode_ClipBidirectional;
3918 else
3919 AssertMsgFailed (("%ls clipboard mode is invalid\n", mode.raw()));
3920 CFGLDRReleaseNode (clipNode);
3921 }
3922 }
3923
3924 return S_OK;
3925}
3926
3927/**
3928 * @param aNode <HardDiskAttachments> node
3929 * @param aRegistered true when the machine is being loaded on VirtualBox
3930 * startup, or when a snapshot is being loaded (wchich
3931 * currently can happen on startup only)
3932 * @param aSnapshotId pointer to the snapshot ID if this is a snapshot machine
3933 */
3934HRESULT Machine::loadHardDisks (CFGNODE aNode, bool aRegistered,
3935 const Guid *aSnapshotId /* = NULL */)
3936{
3937 AssertReturn (aNode, E_INVALIDARG);
3938 AssertReturn ((mType == IsMachine && aSnapshotId == NULL) ||
3939 (mType == IsSnapshotMachine && aSnapshotId != NULL), E_FAIL);
3940
3941 HRESULT rc = S_OK;
3942
3943 unsigned cbDisks = 0;
3944 CFGLDRCountChildren (aNode, "HardDiskAttachment", &cbDisks);
3945
3946 if (!aRegistered && cbDisks > 0)
3947 {
3948 /* when the machine is being loaded (opened) from a file, it cannot
3949 * have hard disks attached (this should not happen normally,
3950 * because we don't allow to attach hard disks to an unregistered
3951 * VM at all */
3952 return setError (E_FAIL,
3953 tr ("Unregistered machine '%ls' cannot have hard disks attached "
3954 "(found %d hard disk attachments)"),
3955 mUserData->mName.raw(), cbDisks);
3956 }
3957
3958 for (unsigned i = 0; i < cbDisks && SUCCEEDED (rc); ++ i)
3959 {
3960 CFGNODE hdNode;
3961 CFGLDRGetChildNode (aNode, "HardDiskAttachment", i, &hdNode);
3962 ComAssertRet (hdNode, E_FAIL);
3963
3964 do
3965 {
3966 /* hardDisk uuid (required) */
3967 Guid uuid;
3968 CFGLDRQueryUUID (hdNode, "hardDisk", uuid.ptr());
3969 /* bus (controller) type (required) */
3970 Bstr bus;
3971 CFGLDRQueryBSTR (hdNode, "bus", bus.asOutParam());
3972 /* device (required) */
3973 Bstr device;
3974 CFGLDRQueryBSTR (hdNode, "device", device.asOutParam());
3975
3976 /* find a hard disk by UUID */
3977 ComObjPtr <HardDisk> hd;
3978 rc = mParent->getHardDisk (uuid, hd);
3979 if (FAILED (rc))
3980 break;
3981
3982 AutoLock hdLock (hd);
3983
3984 if (!hd->machineId().isEmpty())
3985 {
3986 rc = setError (E_FAIL,
3987 tr ("Hard disk '%ls' with UUID {%s} is already "
3988 "attached to a machine with UUID {%s} (see '%ls')"),
3989 hd->toString().raw(), uuid.toString().raw(),
3990 hd->machineId().toString().raw(),
3991 mData->mConfigFileFull.raw());
3992 break;
3993 }
3994
3995 if (hd->type() == HardDiskType_ImmutableHardDisk)
3996 {
3997 rc = setError (E_FAIL,
3998 tr ("Immutable hard disk '%ls' with UUID {%s} cannot be "
3999 "directly attached to a machine (see '%ls')"),
4000 hd->toString().raw(), uuid.toString().raw(),
4001 mData->mConfigFileFull.raw());
4002 break;
4003 }
4004
4005 /* attach the device */
4006 DiskControllerType_T ctl = DiskControllerType_InvalidController;
4007 LONG dev = -1;
4008
4009 if (bus == L"ide0")
4010 {
4011 ctl = DiskControllerType_IDE0Controller;
4012 if (device == L"master")
4013 dev = 0;
4014 else if (device == L"slave")
4015 dev = 1;
4016 else
4017 ComAssertMsgFailedBreak (("Invalid device: %ls\n", device.raw()),
4018 rc = E_FAIL);
4019 }
4020 else if (bus == L"ide1")
4021 {
4022 ctl = DiskControllerType_IDE1Controller;
4023 if (device == L"master")
4024 rc = setError (E_FAIL, tr("Could not attach a disk as a master "
4025 "device on the secondary controller"));
4026 else if (device == L"slave")
4027 dev = 1;
4028 else
4029 ComAssertMsgFailedBreak (("Invalid device: %ls\n", device.raw()),
4030 rc = E_FAIL);
4031 }
4032 else
4033 ComAssertMsgFailedBreak (("Invalid bus: %ls\n", bus.raw()),
4034 rc = E_FAIL);
4035
4036 ComObjPtr <HardDiskAttachment> attachment;
4037 attachment.createObject();
4038 rc = attachment->init (hd, ctl, dev, false /* aDirty */);
4039 if (FAILED (rc))
4040 break;
4041
4042 /* associate the hard disk with this machine */
4043 hd->setMachineId (mData->mUuid);
4044
4045 /* associate the hard disk with the given snapshot ID */
4046 if (mType == IsSnapshotMachine)
4047 hd->setSnapshotId (*aSnapshotId);
4048
4049 mHDData->mHDAttachments.push_back (attachment);
4050 }
4051 while (0);
4052
4053 CFGLDRReleaseNode (hdNode);
4054 }
4055
4056 return rc;
4057}
4058
4059/**
4060 * Creates a config loader and loads the settings file.
4061 *
4062 * @param aIsNew |true| if a newly created settings file is to be opened
4063 * (must be the case only when called from #saveSettings())
4064 *
4065 * @note
4066 * XML Schema errors are not detected by this method because
4067 * it assumes that it will load settings from an exclusively locked
4068 * file (using a file handle) that was previously validated when opened
4069 * for the first time. Thus, this method should be used only when
4070 * it's necessary to modify (save) the settings file.
4071 *
4072 * @note The object must be locked at least for reading before calling
4073 * this method.
4074 */
4075HRESULT Machine::openConfigLoader (CFGHANDLE *aLoader, bool aIsNew /* = false */)
4076{
4077 AssertReturn (aLoader, E_FAIL);
4078
4079 /* The settings file must be created and locked at this point */
4080 ComAssertRet (isConfigLocked(), E_FAIL);
4081
4082 /* load the config file */
4083 int vrc = CFGLDRLoad (aLoader,
4084 Utf8Str (mData->mConfigFileFull), mData->mHandleCfgFile,
4085 aIsNew ? NULL : XmlSchemaNS, true, cfgLdrEntityResolver,
4086 NULL);
4087 ComAssertRCRet (vrc, E_FAIL);
4088
4089 return S_OK;
4090}
4091
4092/**
4093 * Closes the config loader previously created by #openConfigLoader().
4094 * If \a aSaveBeforeClose is true, then the config is saved to the settings file
4095 * before closing. If saving fails, a proper error message is set.
4096 *
4097 * @param aSaveBeforeClose whether to save the config before closing or not
4098 */
4099HRESULT Machine::closeConfigLoader (CFGHANDLE aLoader, bool aSaveBeforeClose)
4100{
4101 HRESULT rc = S_OK;
4102
4103 if (aSaveBeforeClose)
4104 {
4105 char *loaderError = NULL;
4106 int vrc = CFGLDRSave (aLoader, &loaderError);
4107 if (VBOX_FAILURE (vrc))
4108 {
4109 rc = setError (E_FAIL,
4110 tr ("Could not save the settings file '%ls' (%Vrc)%s%s"),
4111 mData->mConfigFileFull.raw(), vrc,
4112 loaderError ? ".\n" : "", loaderError ? loaderError : "");
4113 if (loaderError)
4114 RTMemTmpFree (loaderError);
4115 }
4116 }
4117
4118 CFGLDRFree (aLoader);
4119
4120 return rc;
4121}
4122
4123/**
4124 * Searches for a <Snapshot> node for the given snapshot.
4125 * If the search is successful, \a aSnapshotNode will contain the found node.
4126 * In this case, \a aSnapshotsNode can be NULL meaning the found node is a
4127 * direct child of \a aMachineNode.
4128 *
4129 * If the search fails, a failure is returned and both \a aSnapshotsNode and
4130 * \a aSnapshotNode are set to 0.
4131 *
4132 * @param aSnapshot snapshot to search for
4133 * @param aMachineNode <Machine> node to start from
4134 * @param aSnapshotsNode <Snapshots> node containing the found <Snapshot> node
4135 * (may be NULL if the caller is not interested)
4136 * @param aSnapshotNode found <Snapshot> node
4137 */
4138HRESULT Machine::findSnapshotNode (Snapshot *aSnapshot, CFGNODE aMachineNode,
4139 CFGNODE *aSnapshotsNode, CFGNODE *aSnapshotNode)
4140{
4141 AssertReturn (aSnapshot && aMachineNode && aSnapshotNode, E_FAIL);
4142
4143 if (aSnapshotsNode)
4144 *aSnapshotsNode = 0;
4145 *aSnapshotNode = 0;
4146
4147 // build the full uuid path (from the fist parent to the given snapshot)
4148 std::list <Guid> path;
4149 {
4150 ComObjPtr <Snapshot> parent = aSnapshot;
4151 while (parent)
4152 {
4153 path.push_front (parent->data().mId);
4154 parent = parent->parent();
4155 }
4156 }
4157
4158 CFGNODE snapshotsNode = aMachineNode;
4159 CFGNODE snapshotNode = 0;
4160
4161 for (std::list <Guid>::const_iterator it = path.begin();
4162 it != path.end();
4163 ++ it)
4164 {
4165 if (snapshotNode)
4166 {
4167 // proceed to the nested <Snapshots> node
4168 Assert (snapshotsNode);
4169 if (snapshotsNode != aMachineNode)
4170 {
4171 CFGLDRReleaseNode (snapshotsNode);
4172 snapshotsNode = 0;
4173 }
4174 CFGLDRGetChildNode (snapshotNode, "Snapshots", 0, &snapshotsNode);
4175 CFGLDRReleaseNode (snapshotNode);
4176 snapshotNode = 0;
4177 }
4178
4179 AssertReturn (snapshotsNode, E_FAIL);
4180
4181 unsigned count = 0, i = 0;
4182 CFGLDRCountChildren (snapshotsNode, "Snapshot", &count);
4183 for (; i < count; ++ i)
4184 {
4185 snapshotNode = 0;
4186 CFGLDRGetChildNode (snapshotsNode, "Snapshot", i, &snapshotNode);
4187 Guid id;
4188 CFGLDRQueryUUID (snapshotNode, "uuid", id.ptr());
4189 if (id == (*it))
4190 {
4191 // we keep (don't release) snapshotNode and snapshotsNode
4192 break;
4193 }
4194 CFGLDRReleaseNode (snapshotNode);
4195 snapshotNode = 0;
4196 }
4197
4198 if (i == count)
4199 {
4200 // the next uuid is not found, no need to continue...
4201 AssertFailed();
4202 if (snapshotsNode != aMachineNode)
4203 {
4204 CFGLDRReleaseNode (snapshotsNode);
4205 snapshotsNode = 0;
4206 }
4207 break;
4208 }
4209 }
4210
4211 // we must always succesfully find the node
4212 AssertReturn (snapshotNode, E_FAIL);
4213 AssertReturn (snapshotsNode, E_FAIL);
4214
4215 if (aSnapshotsNode)
4216 *aSnapshotsNode = snapshotsNode != aMachineNode ? snapshotsNode : 0;
4217 *aSnapshotNode = snapshotNode;
4218
4219 return S_OK;
4220}
4221
4222/**
4223 * Returns the snapshot with the given UUID or fails of no such snapshot.
4224 *
4225 * @param aId snapshot UUID to find (empty UUID refers the first snapshot)
4226 * @param aSnapshot where to return the found snapshot
4227 * @param aSetError true to set extended error info on failure
4228 */
4229HRESULT Machine::findSnapshot (const Guid &aId, ComObjPtr <Snapshot> &aSnapshot,
4230 bool aSetError /* = false */)
4231{
4232 if (!mData->mFirstSnapshot)
4233 {
4234 if (aSetError)
4235 return setError (E_FAIL,
4236 tr ("This machine does not have any snapshots"));
4237 return E_FAIL;
4238 }
4239
4240 if (aId.isEmpty())
4241 aSnapshot = mData->mFirstSnapshot;
4242 else
4243 aSnapshot = mData->mFirstSnapshot->findChildOrSelf (aId);
4244
4245 if (!aSnapshot)
4246 {
4247 if (aSetError)
4248 return setError (E_FAIL,
4249 tr ("Could not find a snapshot with UUID {%s}"),
4250 aId.toString().raw());
4251 return E_FAIL;
4252 }
4253
4254 return S_OK;
4255}
4256
4257/**
4258 * Returns the snapshot with the given name or fails of no such snapshot.
4259 *
4260 * @param aName snapshot name to find
4261 * @param aSnapshot where to return the found snapshot
4262 * @param aSetError true to set extended error info on failure
4263 */
4264HRESULT Machine::findSnapshot (const BSTR aName, ComObjPtr <Snapshot> &aSnapshot,
4265 bool aSetError /* = false */)
4266{
4267 AssertReturn (aName, E_INVALIDARG);
4268
4269 if (!mData->mFirstSnapshot)
4270 {
4271 if (aSetError)
4272 return setError (E_FAIL,
4273 tr ("This machine does not have any snapshots"));
4274 return E_FAIL;
4275 }
4276
4277 aSnapshot = mData->mFirstSnapshot->findChildOrSelf (aName);
4278
4279 if (!aSnapshot)
4280 {
4281 if (aSetError)
4282 return setError (E_FAIL,
4283 tr ("Could not find a snapshot named '%ls'"), aName);
4284 return E_FAIL;
4285 }
4286
4287 return S_OK;
4288}
4289
4290/**
4291 * Searches for an attachment that contains the given hard disk.
4292 * The hard disk must be associated with some VM and can be optionally
4293 * associated with some snapshot. If the attachment is stored in the snapshot
4294 * (i.e. the hard disk is associated with some snapshot), @a aSnapshot
4295 * will point to a non-null object on output.
4296 *
4297 * @param aHd hard disk to search an attachment for
4298 * @param aMachine where to store the hard disk's machine (can be NULL)
4299 * @param aSnapshot where to store the hard disk's snapshot (can be NULL)
4300 * @param aHda where to store the hard disk's attachment (can be NULL)
4301 *
4302 *
4303 * @note
4304 * It is assumed that the machine where the attachment is found,
4305 * is already placed to the Discarding state, when this method is called.
4306 * @note
4307 * The object returned in @a aHda is the attachment from the snapshot
4308 * machine if the hard disk is associated with the snapshot, not from the
4309 * primary machine object returned returned in @a aMachine.
4310 */
4311HRESULT Machine::findHardDiskAttachment (const ComObjPtr <HardDisk> &aHd,
4312 ComObjPtr <Machine> *aMachine,
4313 ComObjPtr <Snapshot> *aSnapshot,
4314 ComObjPtr <HardDiskAttachment> *aHda)
4315{
4316 AssertReturn (!aHd.isNull(), E_INVALIDARG);
4317
4318 Guid mid = aHd->machineId();
4319 Guid sid = aHd->snapshotId();
4320
4321 AssertReturn (!mid.isEmpty(), E_INVALIDARG);
4322
4323 ComObjPtr <Machine> m;
4324 mParent->getMachine (mid, m);
4325 ComAssertRet (!m.isNull(), E_FAIL);
4326
4327 HDData::HDAttachmentList *attachments = &m->mHDData->mHDAttachments;
4328
4329 ComObjPtr <Snapshot> s;
4330 if (!sid.isEmpty())
4331 {
4332 m->findSnapshot (sid, s);
4333 ComAssertRet (!s.isNull(), E_FAIL);
4334 attachments = &s->data().mMachine->mHDData->mHDAttachments;
4335 }
4336
4337 AssertReturn (attachments, E_FAIL);
4338
4339 for (HDData::HDAttachmentList::const_iterator it = attachments->begin();
4340 it != attachments->end();
4341 ++ it)
4342 {
4343 if ((*it)->hardDisk() == aHd)
4344 {
4345 if (aMachine) *aMachine = m;
4346 if (aSnapshot) *aSnapshot = s;
4347 if (aHda) *aHda = (*it);
4348 return S_OK;
4349 }
4350 }
4351
4352 ComAssertFailed();
4353 return E_FAIL;
4354}
4355
4356/**
4357 * Helper for #saveSettings. Cares about renaming the settings directory and
4358 * file if the machine name was changed and about creating a new settings file
4359 * if this is a new machine.
4360 *
4361 * @note Must be never called directly.
4362 *
4363 * @param aRenamed receives |true| if the name was changed and the settings
4364 * file was renamed as a result, or |false| otherwise. The
4365 * value makes sense only on success.
4366 * @param aNew receives |true| if a virgin settings file was created.
4367 */
4368HRESULT Machine::prepareSaveSettings (bool &aRenamed, bool &aNew)
4369{
4370 HRESULT rc = S_OK;
4371
4372 aRenamed = false;
4373
4374 /* if we're ready and isConfigLocked() is FALSE then it means
4375 * that no config file exists yet (we will create a virgin one) */
4376 aNew = !isConfigLocked();
4377
4378 /* attempt to rename the settings file if machine name is changed */
4379 if (mUserData->mNameSync &&
4380 mUserData.isBackedUp() &&
4381 mUserData.backedUpData()->mName != mUserData->mName)
4382 {
4383 aRenamed = true;
4384
4385 if (!aNew)
4386 {
4387 /* unlock the old config file */
4388 rc = unlockConfig();
4389 CheckComRCReturnRC (rc);
4390 }
4391
4392 bool dirRenamed = false;
4393 bool fileRenamed = false;
4394
4395 Utf8Str configFile, newConfigFile;
4396 Utf8Str configDir, newConfigDir;
4397
4398 do
4399 {
4400 int vrc = VINF_SUCCESS;
4401
4402 Utf8Str name = mUserData.backedUpData()->mName;
4403 Utf8Str newName = mUserData->mName;
4404
4405 configFile = mData->mConfigFileFull;
4406
4407 /* first, rename the directory if it matches the machine name */
4408 configDir = configFile;
4409 RTPathStripFilename (configDir.mutableRaw());
4410 newConfigDir = configDir;
4411 if (RTPathFilename (configDir) == name)
4412 {
4413 RTPathStripFilename (newConfigDir.mutableRaw());
4414 newConfigDir = Utf8StrFmt ("%s%c%s",
4415 newConfigDir.raw(), RTPATH_DELIMITER, newName.raw());
4416 /* new dir and old dir cannot be equal here because of 'if'
4417 * above and because name != newName */
4418 Assert (configDir != newConfigDir);
4419 if (!aNew)
4420 {
4421 /* perform real rename only if the machine is not new */
4422 vrc = RTPathRename (configDir.raw(), newConfigDir.raw(), 0);
4423 if (VBOX_FAILURE (vrc))
4424 {
4425 rc = setError (E_FAIL,
4426 tr ("Could not rename the directory '%s' to '%s' "
4427 "to save the settings file (%Vrc)"),
4428 configDir.raw(), newConfigDir.raw(), vrc);
4429 break;
4430 }
4431 dirRenamed = true;
4432 }
4433 }
4434
4435 newConfigFile = Utf8StrFmt ("%s%c%s.xml",
4436 newConfigDir.raw(), RTPATH_DELIMITER, newName.raw());
4437
4438 /* then try to rename the settings file itself */
4439 if (newConfigFile != configFile)
4440 {
4441 /* get the path to old settings file in renamed directory */
4442 configFile = Utf8StrFmt ("%s%c%s",
4443 newConfigDir.raw(), RTPATH_DELIMITER,
4444 RTPathFilename (configFile));
4445 if (!aNew)
4446 {
4447 /* perform real rename only if the machine is not new */
4448 vrc = RTFileRename (configFile.raw(), newConfigFile.raw(), 0);
4449 if (VBOX_FAILURE (vrc))
4450 {
4451 rc = setError (E_FAIL,
4452 tr ("Could not rename the settings file '%s' to '%s' "
4453 "(%Vrc)"),
4454 configFile.raw(), newConfigFile.raw(), vrc);
4455 break;
4456 }
4457 fileRenamed = true;
4458 }
4459 }
4460
4461 /* update mConfigFileFull amd mConfigFile */
4462 Bstr oldConfigFileFull = mData->mConfigFileFull;
4463 Bstr oldConfigFile = mData->mConfigFile;
4464 mData->mConfigFileFull = newConfigFile;
4465 /* try to get the relative path for mConfigFile */
4466 Utf8Str path = newConfigFile;
4467 mParent->calculateRelativePath (path, path);
4468 mData->mConfigFile = path;
4469
4470 /* last, try to update the global settings with the new path */
4471 if (mData->mRegistered)
4472 {
4473 rc = mParent->updateSettings (configDir, newConfigDir);
4474 if (FAILED (rc))
4475 {
4476 /* revert to old values */
4477 mData->mConfigFileFull = oldConfigFileFull;
4478 mData->mConfigFile = oldConfigFile;
4479 break;
4480 }
4481 }
4482
4483 /* update the snapshot folder */
4484 path = mUserData->mSnapshotFolderFull;
4485 if (RTPathStartsWith (path, configDir))
4486 {
4487 path = Utf8StrFmt ("%s%s", newConfigDir.raw(),
4488 path.raw() + configDir.length());
4489 mUserData->mSnapshotFolderFull = path;
4490 calculateRelativePath (path, path);
4491 mUserData->mSnapshotFolder = path;
4492 }
4493
4494 /* update the saved state file path */
4495 path = mSSData->mStateFilePath;
4496 if (RTPathStartsWith (path, configDir))
4497 {
4498 path = Utf8StrFmt ("%s%s", newConfigDir.raw(),
4499 path.raw() + configDir.length());
4500 mSSData->mStateFilePath = path;
4501 }
4502
4503 /* Update saved state file paths of all online snapshots.
4504 * Note that saveSettings() will recognize name change
4505 * and will save all snapshots in this case. */
4506 if (mData->mFirstSnapshot)
4507 mData->mFirstSnapshot->updateSavedStatePaths (configDir,
4508 newConfigDir);
4509 }
4510 while (0);
4511
4512 if (FAILED (rc))
4513 {
4514 /* silently try to rename everything back */
4515 if (fileRenamed)
4516 RTFileRename (newConfigFile.raw(), configFile.raw(), 0);
4517 if (dirRenamed)
4518 RTPathRename (newConfigDir.raw(), configDir.raw(), 0);
4519 }
4520
4521 if (!aNew)
4522 {
4523 /* lock the config again */
4524 HRESULT rc2 = lockConfig();
4525 if (SUCCEEDED (rc))
4526 rc = rc2;
4527 }
4528
4529 CheckComRCReturnRC (rc);
4530 }
4531
4532 if (aNew)
4533 {
4534 /* create a virgin config file */
4535 int vrc = VINF_SUCCESS;
4536
4537 /* ensure the settings directory exists */
4538 Utf8Str path = mData->mConfigFileFull;
4539 RTPathStripFilename (path.mutableRaw());
4540 if (!RTDirExists (path))
4541 {
4542 vrc = RTDirCreateFullPath (path, 0777);
4543 if (VBOX_FAILURE (vrc))
4544 {
4545 return setError (E_FAIL,
4546 tr ("Could not create a directory '%s' "
4547 "to save the settings file (%Vrc)"),
4548 path.raw(), vrc);
4549 }
4550 }
4551
4552 /* Note: open flags must correlated with RTFileOpen() in lockConfig() */
4553 path = Utf8Str (mData->mConfigFileFull);
4554 vrc = RTFileOpen (&mData->mHandleCfgFile, path,
4555 RTFILE_O_READWRITE | RTFILE_O_CREATE |
4556 RTFILE_O_DENY_WRITE);
4557 if (VBOX_SUCCESS (vrc))
4558 {
4559 vrc = RTFileWrite (mData->mHandleCfgFile,
4560 (void *) DefaultMachineConfig,
4561 sizeof (DefaultMachineConfig), NULL);
4562 }
4563 if (VBOX_FAILURE (vrc))
4564 {
4565 mData->mHandleCfgFile = NIL_RTFILE;
4566 return setError (E_FAIL,
4567 tr ("Could not create the settings file '%s' (%Vrc)"),
4568 path.raw(), vrc);
4569 }
4570 /* we do not close the file to simulate lockConfig() */
4571 }
4572
4573 return rc;
4574}
4575
4576/**
4577 * Saves machine data, user data and hardware data.
4578 *
4579 * @param aMarkCurStateAsModified
4580 * if true (default), mData->mCurrentStateModified will be set to
4581 * what #isReallyModified() returns prior to saving settings to a file,
4582 * otherwise the current value of mData->mCurrentStateModified will be
4583 * saved.
4584 * @param aInformCallbacksAnyway
4585 * if true, callbacks will be informed even if #isReallyModified()
4586 * returns false. This is necessary for cases when we change machine data
4587 * diectly, not through the backup()/commit() mechanism.
4588 *
4589 * @note Locks mParent (only in some cases, and only when #isConfigLocked() is
4590 * |TRUE|, see the #prepareSaveSettings() code for details) +
4591 * this object + children for writing.
4592 */
4593HRESULT Machine::saveSettings (bool aMarkCurStateAsModified /* = true */,
4594 bool aInformCallbacksAnyway /* = false */)
4595{
4596 LogFlowThisFuncEnter();
4597
4598 /// @todo (dmik) I guess we should lock all our child objects here
4599 // (such as mVRDPServer etc.) to ensure they are not changed
4600 // until completely saved to disk and committed
4601
4602 /// @todo (dmik) also, we need to delegate saving child objects' settings
4603 // to objects themselves to ensure operations 'commit + save changes'
4604 // are atomic (amd done from the object's lock so that nobody can change
4605 // settings again until completely saved).
4606
4607 AssertReturn (mType == IsMachine || mType == IsSessionMachine, E_FAIL);
4608
4609 bool wasModified;
4610
4611 if (aMarkCurStateAsModified)
4612 {
4613 /*
4614 * We ignore changes to user data when setting mCurrentStateModified
4615 * because the current state will not differ from the current snapshot
4616 * if only user data has been changed (user data is shared by all
4617 * snapshots).
4618 */
4619 mData->mCurrentStateModified = isReallyModified (true /* aIgnoreUserData */);
4620 wasModified = mUserData.hasActualChanges() || mData->mCurrentStateModified;
4621 }
4622 else
4623 {
4624 wasModified = isReallyModified();
4625 }
4626
4627 HRESULT rc = S_OK;
4628
4629 /* First, prepare to save settings. It will will care about renaming the
4630 * settings directory and file if the machine name was changed and about
4631 * creating a new settings file if this is a new machine. */
4632 bool isRenamed = false;
4633 bool isNew = false;
4634 rc = prepareSaveSettings (isRenamed, isNew);
4635 CheckComRCReturnRC (rc);
4636
4637 /* then, open the settings file */
4638 CFGHANDLE configLoader = 0;
4639 rc = openConfigLoader (&configLoader, isNew);
4640 CheckComRCReturnRC (rc);
4641
4642 /* save all snapshots when the machine name was changed since
4643 * it may affect saved state file paths for online snapshots (see
4644 * #openConfigLoader() for details) */
4645 bool updateAllSnapshots = isRenamed;
4646
4647 /* commit before saving, since it may change settings
4648 * (for example, perform fixup of lazy hard disk changes) */
4649 rc = commit();
4650 if (FAILED (rc))
4651 {
4652 closeConfigLoader (configLoader, false /* aSaveBeforeClose */);
4653 return rc;
4654 }
4655
4656 /* include hard disk changes to the modified flag */
4657 wasModified |= mHDData->mHDAttachmentsChanged;
4658 if (aMarkCurStateAsModified)
4659 mData->mCurrentStateModified |= BOOL (mHDData->mHDAttachmentsChanged);
4660
4661
4662 CFGNODE machineNode = 0;
4663 /* create if not exists */
4664 CFGLDRCreateNode (configLoader, "VirtualBox/Machine", &machineNode);
4665
4666 do
4667 {
4668 ComAssertBreak (machineNode, rc = E_FAIL);
4669
4670 /* uuid (required) */
4671 Assert (mData->mUuid);
4672 CFGLDRSetUUID (machineNode, "uuid", mData->mUuid.raw());
4673
4674 /* name (required) */
4675 Assert (!mUserData->mName.isEmpty());
4676 CFGLDRSetBSTR (machineNode, "name", mUserData->mName);
4677
4678 /* nameSync (optional, default is true) */
4679 if (!mUserData->mNameSync)
4680 CFGLDRSetBool (machineNode, "nameSync", false);
4681 else
4682 CFGLDRDeleteAttribute (machineNode, "nameSync");
4683
4684 /* OSType (required) */
4685 {
4686 Bstr osTypeID;
4687 rc = mUserData->mOSType->COMGETTER(Id) (osTypeID.asOutParam());
4688 ComAssertComRCBreak (rc, rc = rc);
4689 Assert (!osTypeID.isNull());
4690 CFGLDRSetBSTR (machineNode, "OSType", osTypeID);
4691 }
4692
4693 /* stateFile (optional) */
4694 if (mData->mMachineState == MachineState_Saved)
4695 {
4696 Assert (!mSSData->mStateFilePath.isEmpty());
4697 /* try to make the file name relative to the settings file dir */
4698 Utf8Str stateFilePath = mSSData->mStateFilePath;
4699 calculateRelativePath (stateFilePath, stateFilePath);
4700 CFGLDRSetString (machineNode, "stateFile", stateFilePath);
4701 }
4702 else
4703 {
4704 Assert (mSSData->mStateFilePath.isNull());
4705 CFGLDRDeleteAttribute (machineNode, "stateFile");
4706 }
4707
4708 /* currentSnapshot ID (optional) */
4709 if (!mData->mCurrentSnapshot.isNull())
4710 {
4711 Assert (!mData->mFirstSnapshot.isNull());
4712 CFGLDRSetUUID (machineNode, "currentSnapshot",
4713 mData->mCurrentSnapshot->data().mId);
4714 }
4715 else
4716 {
4717 Assert (mData->mFirstSnapshot.isNull());
4718 CFGLDRDeleteAttribute (machineNode, "currentSnapshot");
4719 }
4720
4721 /* snapshotFolder (optional) */
4722 if (mUserData->mSnapshotFolder)
4723 CFGLDRSetBSTR (machineNode, "snapshotFolder", mUserData->mSnapshotFolder);
4724 else
4725 CFGLDRDeleteAttribute (machineNode, "snapshotFolder");
4726
4727 /* currentStateModified (optional, default is yes) */
4728 if (!mData->mCurrentStateModified)
4729 CFGLDRSetBool (machineNode, "currentStateModified", false);
4730 else
4731 CFGLDRDeleteAttribute (machineNode, "currentStateModified");
4732
4733 /* lastStateChange */
4734 CFGLDRSetDateTime (machineNode, "lastStateChange",
4735 mData->mLastStateChange);
4736
4737 /* Hardware node (required) */
4738 {
4739 CFGNODE hwNode = 0;
4740 CFGLDRGetChildNode (machineNode, "Hardware", 0, &hwNode);
4741 /* first, delete the entire node if exists */
4742 if (hwNode)
4743 CFGLDRDeleteNode (hwNode);
4744 /* then recreate it */
4745 hwNode = 0;
4746 CFGLDRCreateChildNode (machineNode, "Hardware", &hwNode);
4747 ComAssertBreak (hwNode, rc = E_FAIL);
4748
4749 rc = saveHardware (hwNode);
4750
4751 CFGLDRReleaseNode (hwNode);
4752 if (FAILED (rc))
4753 break;
4754 }
4755
4756 /* HardDiskAttachments node (required) */
4757 {
4758 CFGNODE hdasNode = 0;
4759 CFGLDRGetChildNode (machineNode, "HardDiskAttachments", 0, &hdasNode);
4760 /* first, delete the entire node if exists */
4761 if (hdasNode)
4762 CFGLDRDeleteNode (hdasNode);
4763 /* then recreate it */
4764 hdasNode = 0;
4765 CFGLDRCreateChildNode (machineNode, "HardDiskAttachments", &hdasNode);
4766 ComAssertBreak (hdasNode, rc = E_FAIL);
4767
4768 rc = saveHardDisks (hdasNode);
4769
4770 CFGLDRReleaseNode (hdasNode);
4771 if (FAILED (rc))
4772 break;
4773 }
4774
4775 /* update all snapshots if requested */
4776 if (updateAllSnapshots)
4777 rc = saveSnapshotSettingsWorker (machineNode, NULL,
4778 SaveSS_UpdateAllOp);
4779 }
4780 while (0);
4781
4782 if (machineNode)
4783 CFGLDRReleaseNode (machineNode);
4784
4785 if (SUCCEEDED (rc))
4786 rc = closeConfigLoader (configLoader, true /* aSaveBeforeClose */);
4787 else
4788 closeConfigLoader (configLoader, false /* aSaveBeforeClose */);
4789
4790 if (FAILED (rc))
4791 {
4792 /*
4793 * backup arbitrary data item to cause #isModified() to still return
4794 * true in case of any error
4795 */
4796 mHWData.backup();
4797 }
4798
4799 if (wasModified || aInformCallbacksAnyway)
4800 {
4801 /*
4802 * Fire the data change event, even on failure (since we've already
4803 * committed all data). This is done only for SessionMachines because
4804 * mutable Machine instances are always not registered (i.e. private
4805 * to the client process that creates them) and thus don't need to
4806 * inform callbacks.
4807 */
4808 if (mType == IsSessionMachine)
4809 mParent->onMachineDataChange (mData->mUuid);
4810 }
4811
4812 LogFlowThisFunc (("rc=%08X\n", rc));
4813 LogFlowThisFuncLeave();
4814 return rc;
4815}
4816
4817/**
4818 * Wrapper for #saveSnapshotSettingsWorker() that opens the settings file
4819 * and locates the <Machine> node in there. See #saveSnapshotSettingsWorker()
4820 * for more details.
4821 *
4822 * @param aSnapshot Snapshot to operate on
4823 * @param aOpFlags Operation to perform, one of SaveSS_NoOp, SaveSS_AddOp
4824 * or SaveSS_UpdateAttrsOp possibly combined with
4825 * SaveSS_UpdateCurrentId.
4826 *
4827 * @note Locks this object for writing + other child objects.
4828 */
4829HRESULT Machine::saveSnapshotSettings (Snapshot *aSnapshot, int aOpFlags)
4830{
4831 AutoCaller autoCaller (this);
4832 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
4833
4834 AssertReturn (mType == IsMachine || mType == IsSessionMachine, E_FAIL);
4835
4836 AutoLock alock (this);
4837
4838 AssertReturn (isConfigLocked(), E_FAIL);
4839
4840 HRESULT rc = S_OK;
4841
4842 /* load the config file */
4843 CFGHANDLE configLoader = 0;
4844 rc = openConfigLoader (&configLoader);
4845 if (FAILED (rc))
4846 return rc;
4847
4848 CFGNODE machineNode = 0;
4849 CFGLDRGetNode (configLoader, "VirtualBox/Machine", 0, &machineNode);
4850
4851 do
4852 {
4853 ComAssertBreak (machineNode, rc = E_FAIL);
4854
4855 rc = saveSnapshotSettingsWorker (machineNode, aSnapshot, aOpFlags);
4856
4857 CFGLDRReleaseNode (machineNode);
4858 }
4859 while (0);
4860
4861 if (SUCCEEDED (rc))
4862 rc = closeConfigLoader (configLoader, true /* aSaveBeforeClose */);
4863 else
4864 closeConfigLoader (configLoader, false /* aSaveBeforeClose */);
4865
4866 return rc;
4867}
4868
4869/**
4870 * Performs the specified operation on the given snapshot
4871 * in the settings file represented by \a aMachineNode.
4872 *
4873 * If \a aOpFlags = SaveSS_UpdateAllOp, \a aSnapshot can be NULL to indicate
4874 * that the whole tree of the snapshots should be updated in <Machine>.
4875 * One particular case is when the last (and the only) snapshot should be
4876 * removed (it is so when both mCurrentSnapshot and mFirstSnapshot are NULL).
4877 *
4878 * \a aOp may be just SaveSS_UpdateCurrentId if only the currentSnapshot
4879 * attribute of <Machine> needs to be updated.
4880 *
4881 * @param aMachineNode <Machine> node in the opened settings file
4882 * @param aSnapshot Snapshot to operate on
4883 * @param aOpFlags Operation to perform, one of SaveSS_NoOp, SaveSS_AddOp
4884 * or SaveSS_UpdateAttrsOp possibly combined with
4885 * SaveSS_UpdateCurrentId.
4886 *
4887 * @note Must be called with this object locked for writing.
4888 * Locks child objects.
4889 */
4890HRESULT Machine::saveSnapshotSettingsWorker (CFGNODE aMachineNode,
4891 Snapshot *aSnapshot, int aOpFlags)
4892{
4893 AssertReturn (aMachineNode, E_FAIL);
4894
4895 AssertReturn (isLockedOnCurrentThread(), E_FAIL);
4896
4897 int op = aOpFlags & SaveSS_OpMask;
4898 AssertReturn (
4899 (aSnapshot && (op == SaveSS_AddOp || op == SaveSS_UpdateAttrsOp ||
4900 op == SaveSS_UpdateAllOp)) ||
4901 (!aSnapshot && ((op == SaveSS_NoOp && (aOpFlags & SaveSS_UpdateCurrentId)) ||
4902 op == SaveSS_UpdateAllOp)),
4903 E_FAIL);
4904
4905 HRESULT rc = S_OK;
4906
4907 bool recreateWholeTree = false;
4908
4909 do
4910 {
4911 if (op == SaveSS_NoOp)
4912 break;
4913
4914 /* quick path: recreate the whole tree of the snapshots */
4915 if (op == SaveSS_UpdateAllOp && !aSnapshot)
4916 {
4917 /* first, delete the entire root snapshot node if it exists */
4918 CFGNODE snapshotNode = 0;
4919 CFGLDRGetChildNode (aMachineNode, "Snapshot", 0, &snapshotNode);
4920 if (snapshotNode)
4921 CFGLDRDeleteNode (snapshotNode);
4922
4923 /*
4924 * second, if we have any snapshots left, substitute aSnapshot with
4925 * the first snapshot to recreate the whole tree, otherwise break
4926 */
4927 if (mData->mFirstSnapshot)
4928 {
4929 aSnapshot = mData->mFirstSnapshot;
4930 recreateWholeTree = true;
4931 }
4932 else
4933 break;
4934 }
4935
4936 Assert (!!aSnapshot);
4937 ComObjPtr <Snapshot> parent = aSnapshot->parent();
4938
4939 if (op == SaveSS_AddOp)
4940 {
4941 CFGNODE parentNode = 0;
4942
4943 if (parent)
4944 {
4945 rc = findSnapshotNode (parent, aMachineNode, NULL, &parentNode);
4946 if (FAILED (rc))
4947 break;
4948 ComAssertBreak (parentNode, rc = E_FAIL);
4949 }
4950
4951 do
4952 {
4953 CFGNODE snapshotsNode = 0;
4954
4955 if (parentNode)
4956 {
4957 CFGLDRCreateChildNode (parentNode, "Snapshots", &snapshotsNode);
4958 ComAssertBreak (snapshotsNode, rc = E_FAIL);
4959 }
4960 else
4961 snapshotsNode = aMachineNode;
4962 do
4963 {
4964 CFGNODE snapshotNode = 0;
4965 CFGLDRAppendChildNode (snapshotsNode, "Snapshot", &snapshotNode);
4966 ComAssertBreak (snapshotNode, rc = E_FAIL);
4967 rc = saveSnapshot (snapshotNode, aSnapshot, false /* aAttrsOnly */);
4968 CFGLDRReleaseNode (snapshotNode);
4969
4970 if (FAILED (rc))
4971 break;
4972
4973 /*
4974 * when a new snapshot is added, this means diffs were created
4975 * for every normal/immutable hard disk of the VM, so we need to
4976 * save the current hard disk attachments
4977 */
4978
4979 CFGNODE hdasNode = 0;
4980 CFGLDRGetChildNode (aMachineNode, "HardDiskAttachments", 0, &hdasNode);
4981 if (hdasNode)
4982 CFGLDRDeleteNode (hdasNode);
4983 CFGLDRCreateChildNode (aMachineNode, "HardDiskAttachments", &hdasNode);
4984 ComAssertBreak (hdasNode, rc = E_FAIL);
4985
4986 rc = saveHardDisks (hdasNode);
4987
4988 if (mHDData->mHDAttachments.size() != 0)
4989 {
4990 /*
4991 * If we have one or more attachments then we definitely
4992 * created diffs for them and associated new diffs with
4993 * current settngs. So, since we don't use saveSettings(),
4994 * we need to inform callbacks manually.
4995 */
4996 if (mType == IsSessionMachine)
4997 mParent->onMachineDataChange (mData->mUuid);
4998 }
4999
5000 CFGLDRReleaseNode (hdasNode);
5001 }
5002 while (0);
5003
5004 if (snapshotsNode != aMachineNode)
5005 CFGLDRReleaseNode (snapshotsNode);
5006 }
5007 while (0);
5008
5009 if (parentNode)
5010 CFGLDRReleaseNode (parentNode);
5011
5012 break;
5013 }
5014
5015 Assert (op == SaveSS_UpdateAttrsOp && !recreateWholeTree ||
5016 op == SaveSS_UpdateAllOp);
5017
5018 CFGNODE snapshotsNode = 0;
5019 CFGNODE snapshotNode = 0;
5020
5021 if (!recreateWholeTree)
5022 {
5023 rc = findSnapshotNode (aSnapshot, aMachineNode,
5024 &snapshotsNode, &snapshotNode);
5025 if (FAILED (rc))
5026 break;
5027 ComAssertBreak (snapshotNode, rc = E_FAIL);
5028 }
5029
5030 if (!snapshotsNode)
5031 snapshotsNode = aMachineNode;
5032
5033 if (op == SaveSS_UpdateAttrsOp)
5034 rc = saveSnapshot (snapshotNode, aSnapshot, true /* aAttrsOnly */);
5035 else do
5036 {
5037 if (snapshotNode)
5038 {
5039 CFGLDRDeleteNode (snapshotNode);
5040 snapshotNode = 0;
5041 }
5042 CFGLDRAppendChildNode (snapshotsNode, "Snapshot", &snapshotNode);
5043 ComAssertBreak (snapshotNode, rc = E_FAIL);
5044 rc = saveSnapshot (snapshotNode, aSnapshot, false /* aAttrsOnly */);
5045 }
5046 while (0);
5047
5048 CFGLDRReleaseNode (snapshotNode);
5049 if (snapshotsNode != aMachineNode)
5050 CFGLDRReleaseNode (snapshotsNode);
5051 }
5052 while (0);
5053
5054 if (SUCCEEDED (rc))
5055 {
5056 /* update currentSnapshot when appropriate */
5057 if (aOpFlags & SaveSS_UpdateCurrentId)
5058 {
5059 if (!mData->mCurrentSnapshot.isNull())
5060 CFGLDRSetUUID (aMachineNode, "currentSnapshot",
5061 mData->mCurrentSnapshot->data().mId);
5062 else
5063 CFGLDRDeleteAttribute (aMachineNode, "currentSnapshot");
5064 }
5065 if (aOpFlags & SaveSS_UpdateCurStateModified)
5066 {
5067 if (!mData->mCurrentStateModified)
5068 CFGLDRSetBool (aMachineNode, "currentStateModified", false);
5069 else
5070 CFGLDRDeleteAttribute (aMachineNode, "currentStateModified");
5071 }
5072 }
5073
5074 return rc;
5075}
5076
5077/**
5078 * Saves the given snapshot and all its children (unless \a aAttrsOnly is true).
5079 * It is assumed that the given node is empty (unless \a aAttrsOnly is true).
5080 *
5081 * @param aNode <Snapshot> node to save the snapshot to
5082 * @param aSnapshot snapshot to save
5083 * @param aAttrsOnly if true, only updatge user-changeable attrs
5084 */
5085HRESULT Machine::saveSnapshot (CFGNODE aNode, Snapshot *aSnapshot, bool aAttrsOnly)
5086{
5087 AssertReturn (aNode && aSnapshot, E_INVALIDARG);
5088 AssertReturn (mType == IsMachine || mType == IsSessionMachine, E_FAIL);
5089
5090 /* uuid (required) */
5091 if (!aAttrsOnly)
5092 CFGLDRSetUUID (aNode, "uuid", aSnapshot->data().mId);
5093
5094 /* name (required) */
5095 CFGLDRSetBSTR (aNode, "name", aSnapshot->data().mName);
5096
5097 /* timeStamp (required) */
5098 CFGLDRSetDateTime (aNode, "timeStamp", aSnapshot->data().mTimeStamp);
5099
5100 /* Description node (optional) */
5101 {
5102 CFGNODE descNode = 0;
5103 CFGLDRCreateChildNode (aNode, "Description", &descNode);
5104 CFGLDRSetBSTR (descNode, NULL, aSnapshot->data().mDescription);
5105 CFGLDRReleaseNode (descNode);
5106 }
5107
5108 if (aAttrsOnly)
5109 return S_OK;
5110
5111 /* stateFile (optional) */
5112 if (aSnapshot->stateFilePath())
5113 {
5114 /* try to make the file name relative to the settings file dir */
5115 Utf8Str stateFilePath = aSnapshot->stateFilePath();
5116 calculateRelativePath (stateFilePath, stateFilePath);
5117 CFGLDRSetString (aNode, "stateFile", stateFilePath);
5118 }
5119
5120 {
5121 ComObjPtr <SnapshotMachine> snapshotMachine = aSnapshot->data().mMachine;
5122 ComAssertRet (!snapshotMachine.isNull(), E_FAIL);
5123
5124 /* save hardware */
5125 {
5126 CFGNODE hwNode = 0;
5127 CFGLDRCreateChildNode (aNode, "Hardware", &hwNode);
5128
5129 HRESULT rc = snapshotMachine->saveHardware (hwNode);
5130
5131 CFGLDRReleaseNode (hwNode);
5132 if (FAILED (rc))
5133 return rc;
5134 }
5135
5136 /* save hard disks */
5137 {
5138 CFGNODE hdasNode = 0;
5139 CFGLDRCreateChildNode (aNode, "HardDiskAttachments", &hdasNode);
5140
5141 HRESULT rc = snapshotMachine->saveHardDisks (hdasNode);
5142
5143 CFGLDRReleaseNode (hdasNode);
5144 if (FAILED (rc))
5145 return rc;
5146 }
5147 }
5148
5149 /* save children */
5150 {
5151 AutoLock listLock (aSnapshot->childrenLock());
5152
5153 if (aSnapshot->children().size())
5154 {
5155 CFGNODE snapshotsNode = 0;
5156 CFGLDRCreateChildNode (aNode, "Snapshots", &snapshotsNode);
5157
5158 HRESULT rc = S_OK;
5159
5160 for (Snapshot::SnapshotList::const_iterator it = aSnapshot->children().begin();
5161 it != aSnapshot->children().end() && SUCCEEDED (rc);
5162 ++ it)
5163 {
5164 CFGNODE snapshotNode = 0;
5165 CFGLDRCreateChildNode (snapshotsNode, "Snapshot", &snapshotNode);
5166
5167 rc = saveSnapshot (snapshotNode, (*it), aAttrsOnly);
5168
5169 CFGLDRReleaseNode (snapshotNode);
5170 }
5171
5172 CFGLDRReleaseNode (snapshotsNode);
5173 if (FAILED (rc))
5174 return rc;
5175 }
5176 }
5177
5178 return S_OK;
5179}
5180
5181/**
5182 * Creates Saves the VM hardware configuration.
5183 * It is assumed that the given node is empty.
5184 *
5185 * @param aNode <Hardware> node to save the VM hardware confguration to
5186 */
5187HRESULT Machine::saveHardware (CFGNODE aNode)
5188{
5189 AssertReturn (aNode, E_INVALIDARG);
5190
5191 HRESULT rc = S_OK;
5192
5193 /* CPU */
5194 {
5195 CFGNODE cpuNode = 0;
5196 CFGLDRCreateChildNode (aNode, "CPU", &cpuNode);
5197 CFGNODE hwVirtExNode = 0;
5198 CFGLDRCreateChildNode (cpuNode, "HardwareVirtEx", &hwVirtExNode);
5199 char *value = NULL;
5200 switch (mHWData->mHWVirtExEnabled)
5201 {
5202 case TriStateBool_False:
5203 value = "false";
5204 break;
5205 case TriStateBool_True:
5206 value = "true";
5207 break;
5208 default:
5209 value = "default";
5210 }
5211 CFGLDRSetString (hwVirtExNode, "enabled", value);
5212 CFGLDRReleaseNode (hwVirtExNode);
5213 CFGLDRReleaseNode (cpuNode);
5214 }
5215
5216 /* memory (required) */
5217 {
5218 CFGNODE memoryNode = 0;
5219 CFGLDRCreateChildNode (aNode, "Memory", &memoryNode);
5220 CFGLDRSetUInt32 (memoryNode, "RAMSize", mHWData->mMemorySize);
5221 CFGLDRReleaseNode (memoryNode);
5222 }
5223
5224 /* boot (required) */
5225 do
5226 {
5227 CFGNODE bootNode = 0;
5228 CFGLDRCreateChildNode (aNode, "Boot", &bootNode);
5229
5230 for (ULONG pos = 0; pos < ELEMENTS (mHWData->mBootOrder); pos ++)
5231 {
5232 const char *device = NULL;
5233 switch (mHWData->mBootOrder [pos])
5234 {
5235 case DeviceType_NoDevice:
5236 /* skip, this is allowed for <Order> nodes
5237 * when loading, the default value NoDevice will remain */
5238 continue;
5239 case DeviceType_FloppyDevice: device = "Floppy"; break;
5240 case DeviceType_DVDDevice: device = "DVD"; break;
5241 case DeviceType_HardDiskDevice: device = "HardDisk"; break;
5242 case DeviceType_NetworkDevice: device = "Network"; break;
5243 default:
5244 ComAssertMsgFailedBreak (("Invalid boot device: %d\n",
5245 mHWData->mBootOrder [pos]),
5246 rc = E_FAIL);
5247 }
5248 if (FAILED (rc))
5249 break;
5250
5251 CFGNODE orderNode = 0;
5252 CFGLDRAppendChildNode (bootNode, "Order", &orderNode);
5253
5254 CFGLDRSetUInt32 (orderNode, "position", pos + 1);
5255 CFGLDRSetString (orderNode, "device", device);
5256
5257 CFGLDRReleaseNode (orderNode);
5258 }
5259
5260 CFGLDRReleaseNode (bootNode);
5261 }
5262 while (0);
5263
5264 if (FAILED (rc))
5265 return rc;
5266
5267 /* display (required) */
5268 {
5269 CFGNODE displayNode = 0;
5270 CFGLDRCreateChildNode (aNode, "Display", &displayNode);
5271 CFGLDRSetUInt32 (displayNode, "VRAMSize", mHWData->mVRAMSize);
5272 CFGLDRReleaseNode (displayNode);
5273 }
5274
5275#ifdef VBOX_VRDP
5276 /* VRDP settings (optional) */
5277 /// @todo (dmik) move the code to VRDPServer
5278 /// @todo r=sunlover: moved. dmik, please review.
5279 {
5280 CFGNODE remoteDisplayNode = 0;
5281 CFGLDRCreateChildNode (aNode, "RemoteDisplay", &remoteDisplayNode);
5282
5283 if (remoteDisplayNode)
5284 {
5285 mVRDPServer->saveConfig (remoteDisplayNode);
5286 CFGLDRReleaseNode (remoteDisplayNode);
5287 }
5288 }
5289#endif
5290
5291 /* BIOS (required) */
5292 {
5293 CFGNODE biosNode = 0;
5294 CFGLDRCreateChildNode (aNode, "BIOS", &biosNode);
5295 {
5296 BOOL fSet;
5297 /* ACPI */
5298 CFGNODE acpiNode = 0;
5299 CFGLDRCreateChildNode (biosNode, "ACPI", &acpiNode);
5300 mBIOSSettings->COMGETTER(ACPIEnabled)(&fSet);
5301 CFGLDRSetBool (acpiNode, "enabled", !!fSet);
5302 CFGLDRReleaseNode (acpiNode);
5303
5304 /* IOAPIC */
5305 CFGNODE ioapicNode = 0;
5306 CFGLDRCreateChildNode (biosNode, "IOAPIC", &ioapicNode);
5307 mBIOSSettings->COMGETTER(IOAPICEnabled)(&fSet);
5308 CFGLDRSetBool (ioapicNode, "enabled", !!fSet);
5309 CFGLDRReleaseNode (ioapicNode);
5310
5311 /* BIOS logo (optional) **/
5312 CFGNODE logoNode = 0;
5313 CFGLDRCreateChildNode (biosNode, "Logo", &logoNode);
5314 mBIOSSettings->COMGETTER(LogoFadeIn)(&fSet);
5315 CFGLDRSetBool (logoNode, "fadeIn", !!fSet);
5316 mBIOSSettings->COMGETTER(LogoFadeOut)(&fSet);
5317 CFGLDRSetBool (logoNode, "fadeOut", !!fSet);
5318 ULONG ulDisplayTime;
5319 mBIOSSettings->COMGETTER(LogoDisplayTime)(&ulDisplayTime);
5320 CFGLDRSetUInt32 (logoNode, "displayTime", ulDisplayTime);
5321 Bstr logoPath;
5322 mBIOSSettings->COMGETTER(LogoImagePath)(logoPath.asOutParam());
5323 if (logoPath)
5324 CFGLDRSetBSTR (logoNode, "imagePath", logoPath);
5325 else
5326 CFGLDRDeleteAttribute (logoNode, "imagePath");
5327 CFGLDRReleaseNode (logoNode);
5328
5329 /* boot menu (optional) */
5330 CFGNODE bootMenuNode = 0;
5331 CFGLDRCreateChildNode (biosNode, "BootMenu", &bootMenuNode);
5332 BIOSBootMenuMode_T bootMenuMode;
5333 Bstr bootMenuModeStr;
5334 mBIOSSettings->COMGETTER(BootMenuMode)(&bootMenuMode);
5335 switch (bootMenuMode)
5336 {
5337 case BIOSBootMenuMode_Disabled:
5338 bootMenuModeStr = "disabled";
5339 break;
5340 case BIOSBootMenuMode_MenuOnly:
5341 bootMenuModeStr = "menuonly";
5342 break;
5343 default:
5344 bootMenuModeStr = "messageandmenu";
5345 }
5346 CFGLDRSetBSTR (bootMenuNode, "mode", bootMenuModeStr);
5347 CFGLDRReleaseNode (bootMenuNode);
5348 }
5349 CFGLDRReleaseNode(biosNode);
5350 }
5351
5352 /* DVD drive (required) */
5353 /// @todo (dmik) move the code to DVDDrive
5354 do
5355 {
5356 CFGNODE dvdNode = 0;
5357 CFGLDRCreateChildNode (aNode, "DVDDrive", &dvdNode);
5358
5359 BOOL fPassthrough;
5360 mDVDDrive->COMGETTER(Passthrough)(&fPassthrough);
5361 CFGLDRSetBool(dvdNode, "passthrough", !!fPassthrough);
5362
5363 switch (mDVDDrive->data()->mDriveState)
5364 {
5365 case DriveState_ImageMounted:
5366 {
5367 Assert (!mDVDDrive->data()->mDVDImage.isNull());
5368
5369 Guid id;
5370 rc = mDVDDrive->data()->mDVDImage->COMGETTER(Id) (id.asOutParam());
5371 Assert (!id.isEmpty());
5372
5373 CFGNODE imageNode = 0;
5374 CFGLDRCreateChildNode (dvdNode, "Image", &imageNode);
5375 CFGLDRSetUUID (imageNode, "uuid", id.ptr());
5376 CFGLDRReleaseNode (imageNode);
5377 break;
5378 }
5379 case DriveState_HostDriveCaptured:
5380 {
5381 Assert (!mDVDDrive->data()->mHostDrive.isNull());
5382
5383 Bstr name;
5384 rc = mDVDDrive->data()->mHostDrive->COMGETTER(Name) (name.asOutParam());
5385 Assert (!name.isEmpty());
5386
5387 CFGNODE hostDriveNode = 0;
5388 CFGLDRCreateChildNode (dvdNode, "HostDrive", &hostDriveNode);
5389 CFGLDRSetBSTR (hostDriveNode, "src", name);
5390 CFGLDRReleaseNode (hostDriveNode);
5391 break;
5392 }
5393 case DriveState_NotMounted:
5394 /* do nothing, i.e.leave the DVD drive node empty */
5395 break;
5396 default:
5397 ComAssertMsgFailedBreak (("Invalid DVD drive state: %d\n",
5398 mDVDDrive->data()->mDriveState),
5399 rc = E_FAIL);
5400 }
5401
5402 CFGLDRReleaseNode (dvdNode);
5403 }
5404 while (0);
5405
5406 if (FAILED (rc))
5407 return rc;
5408
5409 /* Flooppy drive (required) */
5410 /// @todo (dmik) move the code to DVDDrive
5411 do
5412 {
5413 CFGNODE floppyNode = 0;
5414 CFGLDRCreateChildNode (aNode, "FloppyDrive", &floppyNode);
5415
5416 BOOL fFloppyEnabled;
5417 rc = mFloppyDrive->COMGETTER(Enabled)(&fFloppyEnabled);
5418 CFGLDRSetBool (floppyNode, "enabled", !!fFloppyEnabled);
5419
5420 switch (mFloppyDrive->data()->mDriveState)
5421 {
5422 case DriveState_ImageMounted:
5423 {
5424 Assert (!mFloppyDrive->data()->mFloppyImage.isNull());
5425
5426 Guid id;
5427 rc = mFloppyDrive->data()->mFloppyImage->COMGETTER(Id) (id.asOutParam());
5428 Assert (!id.isEmpty());
5429
5430 CFGNODE imageNode = 0;
5431 CFGLDRCreateChildNode (floppyNode, "Image", &imageNode);
5432 CFGLDRSetUUID (imageNode, "uuid", id.ptr());
5433 CFGLDRReleaseNode (imageNode);
5434 break;
5435 }
5436 case DriveState_HostDriveCaptured:
5437 {
5438 Assert (!mFloppyDrive->data()->mHostDrive.isNull());
5439
5440 Bstr name;
5441 rc = mFloppyDrive->data()->mHostDrive->COMGETTER(Name) (name.asOutParam());
5442 Assert (!name.isEmpty());
5443
5444 CFGNODE hostDriveNode = 0;
5445 CFGLDRCreateChildNode (floppyNode, "HostDrive", &hostDriveNode);
5446 CFGLDRSetBSTR (hostDriveNode, "src", name);
5447 CFGLDRReleaseNode (hostDriveNode);
5448 break;
5449 }
5450 case DriveState_NotMounted:
5451 /* do nothing, i.e.leave the Floppy drive node empty */
5452 break;
5453 default:
5454 ComAssertMsgFailedBreak (("Invalid Floppy drive state: %d\n",
5455 mFloppyDrive->data()->mDriveState),
5456 rc = E_FAIL);
5457 }
5458
5459 CFGLDRReleaseNode (floppyNode);
5460 }
5461 while (0);
5462
5463 if (FAILED (rc))
5464 return rc;
5465
5466
5467 /* USB Controller (required) */
5468 rc = mUSBController->saveSettings (aNode);
5469 if (FAILED (rc))
5470 return rc;
5471
5472 /* Network adapters (required) */
5473 do
5474 {
5475 CFGNODE nwNode = 0;
5476 CFGLDRCreateChildNode (aNode, "Network", &nwNode);
5477
5478 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
5479 {
5480 CFGNODE networkAdapterNode = 0;
5481 CFGLDRAppendChildNode (nwNode, "Adapter", &networkAdapterNode);
5482
5483 CFGLDRSetUInt32 (networkAdapterNode, "slot", slot);
5484 CFGLDRSetBool (networkAdapterNode, "enabled",
5485 !!mNetworkAdapters [slot]->data()->mEnabled);
5486 CFGLDRSetBSTR (networkAdapterNode, "MACAddress",
5487 mNetworkAdapters [slot]->data()->mMACAddress);
5488 CFGLDRSetBool (networkAdapterNode, "cable",
5489 !!mNetworkAdapters [slot]->data()->mCableConnected);
5490
5491 if (mNetworkAdapters [slot]->data()->mTraceEnabled)
5492 CFGLDRSetBool (networkAdapterNode, "trace", true);
5493
5494 CFGLDRSetBSTR (networkAdapterNode, "tracefile",
5495 mNetworkAdapters [slot]->data()->mTraceFile);
5496
5497 switch (mNetworkAdapters [slot]->data()->mAdapterType)
5498 {
5499 case NetworkAdapterType_NetworkAdapterAm79C970A:
5500 CFGLDRSetString (networkAdapterNode, "type", "Am79C970A");
5501 break;
5502 case NetworkAdapterType_NetworkAdapterAm79C973:
5503 CFGLDRSetString (networkAdapterNode, "type", "Am79C973");
5504 break;
5505 default:
5506 ComAssertMsgFailedBreak (("Invalid network adapter type: %d\n",
5507 mNetworkAdapters [slot]->data()->mAdapterType),
5508 rc = E_FAIL);
5509 }
5510
5511 CFGNODE attachmentNode = 0;
5512 switch (mNetworkAdapters [slot]->data()->mAttachmentType)
5513 {
5514 case NetworkAttachmentType_NoNetworkAttachment:
5515 {
5516 /* do nothing -- empty content */
5517 break;
5518 }
5519 case NetworkAttachmentType_NATNetworkAttachment:
5520 {
5521 CFGLDRAppendChildNode (networkAdapterNode, "NAT", &attachmentNode);
5522 break;
5523 }
5524 case NetworkAttachmentType_HostInterfaceNetworkAttachment:
5525 {
5526 CFGLDRAppendChildNode (networkAdapterNode, "HostInterface", &attachmentNode);
5527 const Bstr &name = mNetworkAdapters [slot]->data()->mHostInterface;
5528#ifdef __WIN__
5529 Assert (!name.isNull());
5530#endif
5531#ifdef VBOX_WITH_UNIXY_TAP_NETWORKING
5532 if (!name.isEmpty())
5533#endif
5534 CFGLDRSetBSTR (attachmentNode, "name", name);
5535#ifdef VBOX_WITH_UNIXY_TAP_NETWORKING
5536 const Bstr &tapSetupApp =
5537 mNetworkAdapters [slot]->data()->mTAPSetupApplication;
5538 if (!tapSetupApp.isEmpty())
5539 CFGLDRSetBSTR (attachmentNode, "TAPSetup", tapSetupApp);
5540 const Bstr &tapTerminateApp =
5541 mNetworkAdapters [slot]->data()->mTAPTerminateApplication;
5542 if (!tapTerminateApp.isEmpty())
5543 CFGLDRSetBSTR (attachmentNode, "TAPTerminate", tapTerminateApp);
5544#endif /* VBOX_WITH_UNIXY_TAP_NETWORKING */
5545 break;
5546 }
5547 case NetworkAttachmentType_InternalNetworkAttachment:
5548 {
5549 CFGLDRAppendChildNode (networkAdapterNode, "InternalNetwork", &attachmentNode);
5550 const Bstr &name = mNetworkAdapters[slot]->data()->mInternalNetwork;
5551 Assert(!name.isNull());
5552 CFGLDRSetBSTR (attachmentNode, "name", name);
5553 break;
5554 }
5555 default:
5556 {
5557 ComAssertFailedBreak (rc = E_FAIL);
5558 break;
5559 }
5560 }
5561 if (attachmentNode)
5562 CFGLDRReleaseNode (attachmentNode);
5563
5564 CFGLDRReleaseNode (networkAdapterNode);
5565 }
5566
5567 CFGLDRReleaseNode (nwNode);
5568 }
5569 while (0);
5570
5571 if (FAILED (rc))
5572 return rc;
5573
5574 /* Audio adapter */
5575 do
5576 {
5577 CFGNODE adapterNode = 0;
5578 CFGLDRCreateChildNode (aNode, "AudioAdapter", &adapterNode);
5579
5580 switch (mAudioAdapter->data()->mAudioDriver)
5581 {
5582 case AudioDriverType_NullAudioDriver:
5583 {
5584 CFGLDRSetString (adapterNode, "driver", "null");
5585 break;
5586 }
5587#ifdef __WIN__
5588 case AudioDriverType_WINMMAudioDriver:
5589#ifdef VBOX_WITH_WINMM
5590 {
5591 CFGLDRSetString (adapterNode, "driver", "winmm");
5592 break;
5593 }
5594#endif
5595 case AudioDriverType_DSOUNDAudioDriver:
5596 {
5597 CFGLDRSetString (adapterNode, "driver", "dsound");
5598 break;
5599 }
5600#endif /* __WIN__ */
5601 case AudioDriverType_ALSAAudioDriver:
5602#ifdef VBOX_WITH_ALSA
5603 {
5604 CFGLDRSetString (adapterNode, "driver", "alsa");
5605 break;
5606 }
5607#endif
5608#ifdef __LINUX__
5609 case AudioDriverType_OSSAudioDriver:
5610 {
5611 CFGLDRSetString (adapterNode, "driver", "oss");
5612 break;
5613 }
5614#endif /* __LINUX__ */
5615 default:
5616 ComAssertMsgFailedBreak (("Wrong audio driver type! driver = %d\n",
5617 mAudioAdapter->data()->mAudioDriver),
5618 rc = E_FAIL);
5619 }
5620
5621 CFGLDRSetBool (adapterNode, "enabled", !!mAudioAdapter->data()->mEnabled);
5622
5623 CFGLDRReleaseNode (adapterNode);
5624 }
5625 while (0);
5626
5627 if (FAILED (rc))
5628 return rc;
5629
5630 /* Shared folders */
5631 do
5632 {
5633 CFGNODE sharedFoldersNode = 0;
5634 CFGLDRCreateChildNode (aNode, "SharedFolders", &sharedFoldersNode);
5635
5636 for (HWData::SharedFolderList::const_iterator it = mHWData->mSharedFolders.begin();
5637 it != mHWData->mSharedFolders.end();
5638 ++ it)
5639 {
5640 ComObjPtr <SharedFolder> folder = *it;
5641
5642 CFGNODE folderNode = 0;
5643 CFGLDRAppendChildNode (sharedFoldersNode, "SharedFolder", &folderNode);
5644
5645 /* all are mandatory */
5646 CFGLDRSetBSTR (folderNode, "name", folder->name());
5647 CFGLDRSetBSTR (folderNode, "hostPath", folder->hostPath());
5648
5649 CFGLDRReleaseNode (folderNode);
5650 }
5651
5652 CFGLDRReleaseNode (sharedFoldersNode);
5653 }
5654 while (0);
5655
5656 /* Clipboard */
5657 {
5658 CFGNODE clipNode = 0;
5659 CFGLDRCreateChildNode (aNode, "Clipboard", &clipNode);
5660
5661 char *mode = "Disabled";
5662 switch (mHWData->mClipboardMode)
5663 {
5664 case ClipboardMode_ClipDisabled:
5665 /* already assigned */
5666 break;
5667 case ClipboardMode_ClipHostToGuest:
5668 mode = "HostToGuest";
5669 break;
5670 case ClipboardMode_ClipGuestToHost:
5671 mode = "GuestToHost";
5672 break;
5673 case ClipboardMode_ClipBidirectional:
5674 mode = "Bidirectional";
5675 break;
5676 default:
5677 AssertMsgFailed (("Clipboard mode %d is invalid",
5678 mHWData->mClipboardMode));
5679 break;
5680 }
5681 CFGLDRSetString (clipNode, "mode", mode);
5682
5683 CFGLDRReleaseNode (clipNode);
5684 }
5685
5686 return rc;
5687}
5688
5689/**
5690 * Saves the hard disk confguration.
5691 * It is assumed that the given node is empty.
5692 *
5693 * @param aNode <HardDiskAttachments> node to save the hard disk confguration to
5694 */
5695HRESULT Machine::saveHardDisks (CFGNODE aNode)
5696{
5697 AssertReturn (aNode, E_INVALIDARG);
5698
5699 HRESULT rc = S_OK;
5700
5701 for (HDData::HDAttachmentList::const_iterator it = mHDData->mHDAttachments.begin();
5702 it != mHDData->mHDAttachments.end() && SUCCEEDED (rc);
5703 ++ it)
5704 {
5705 ComObjPtr <HardDiskAttachment> att = *it;
5706
5707 CFGNODE hdNode = 0;
5708 CFGLDRAppendChildNode (aNode, "HardDiskAttachment", &hdNode);
5709
5710 do
5711 {
5712 const char *bus = NULL;
5713 switch (att->controller())
5714 {
5715 case DiskControllerType_IDE0Controller: bus = "ide0"; break;
5716 case DiskControllerType_IDE1Controller: bus = "ide1"; break;
5717 default:
5718 ComAssertFailedBreak (rc = E_FAIL);
5719 }
5720 if (FAILED (rc))
5721 break;
5722
5723 const char *dev = NULL;
5724 switch (att->deviceNumber())
5725 {
5726 case 0: dev = "master"; break;
5727 case 1: dev = "slave"; break;
5728 default:
5729 ComAssertFailedBreak (rc = E_FAIL);
5730 }
5731 if (FAILED (rc))
5732 break;
5733
5734 CFGLDRSetUUID (hdNode, "hardDisk", att->hardDisk()->id());
5735 CFGLDRSetString (hdNode, "bus", bus);
5736 CFGLDRSetString (hdNode, "device", dev);
5737 }
5738 while (0);
5739
5740 CFGLDRReleaseNode (hdNode);
5741 }
5742
5743 return rc;
5744}
5745
5746/**
5747 * Saves machine state settings as defined by aFlags
5748 * (SaveSTS_* values).
5749 *
5750 * @param aFlags a combination of SaveSTS_* flags
5751 *
5752 * @note Locks objects!
5753 */
5754HRESULT Machine::saveStateSettings (int aFlags)
5755{
5756 if (aFlags == 0)
5757 return S_OK;
5758
5759 AutoCaller autoCaller (this);
5760 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
5761
5762 AutoLock alock (this);
5763
5764 /* load the config file */
5765 CFGHANDLE configLoader = 0;
5766 HRESULT rc = openConfigLoader (&configLoader);
5767 if (FAILED (rc))
5768 return rc;
5769
5770 CFGNODE machineNode = 0;
5771 CFGLDRGetNode (configLoader, "VirtualBox/Machine", 0, &machineNode);
5772
5773 do
5774 {
5775 ComAssertBreak (machineNode, rc = E_FAIL);
5776
5777 if (aFlags & SaveSTS_CurStateModified)
5778 {
5779 if (!mData->mCurrentStateModified)
5780 CFGLDRSetBool (machineNode, "currentStateModified", false);
5781 else
5782 CFGLDRDeleteAttribute (machineNode, "currentStateModified");
5783 }
5784
5785 if (aFlags & SaveSTS_StateFilePath)
5786 {
5787 if (mSSData->mStateFilePath)
5788 CFGLDRSetBSTR (machineNode, "stateFile", mSSData->mStateFilePath);
5789 else
5790 CFGLDRDeleteAttribute (machineNode, "stateFile");
5791 }
5792
5793 if (aFlags & SaveSTS_StateTimeStamp)
5794 {
5795 Assert (mData->mMachineState != MachineState_Aborted ||
5796 mSSData->mStateFilePath.isNull());
5797
5798 CFGLDRSetDateTime (machineNode, "lastStateChange",
5799 mData->mLastStateChange);
5800
5801 // set the aborted attribute when appropriate
5802 if (mData->mMachineState == MachineState_Aborted)
5803 CFGLDRSetBool (machineNode, "aborted", true);
5804 else
5805 CFGLDRDeleteAttribute (machineNode, "aborted");
5806 }
5807 }
5808 while (0);
5809
5810 if (machineNode)
5811 CFGLDRReleaseNode (machineNode);
5812
5813 if (SUCCEEDED (rc))
5814 rc = closeConfigLoader (configLoader, true /* aSaveBeforeClose */);
5815 else
5816 closeConfigLoader (configLoader, false /* aSaveBeforeClose */);
5817
5818 return rc;
5819}
5820
5821/**
5822 * Cleans up all differencing hard disks based on immutable hard disks.
5823 *
5824 * @note Locks objects!
5825 */
5826HRESULT Machine::wipeOutImmutableDiffs()
5827{
5828 AutoCaller autoCaller (this);
5829 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
5830
5831 AutoReaderLock alock (this);
5832
5833 AssertReturn (mData->mMachineState == MachineState_PoweredOff ||
5834 mData->mMachineState == MachineState_Aborted, E_FAIL);
5835
5836 for (HDData::HDAttachmentList::const_iterator it = mHDData->mHDAttachments.begin();
5837 it != mHDData->mHDAttachments.end();
5838 ++ it)
5839 {
5840 ComObjPtr <HardDisk> hd = (*it)->hardDisk();
5841 AutoLock hdLock (hd);
5842
5843 if(hd->isParentImmutable())
5844 {
5845 /// @todo (dmik) no error handling for now
5846 // (need async error reporting for this)
5847 hd->asVDI()->wipeOutImage();
5848 }
5849 }
5850
5851 return S_OK;
5852}
5853
5854/**
5855 * Fixes up lazy hard disk attachments by creating or deleting differencing
5856 * hard disks when machine settings are being committed.
5857 * Must be called only from #commit().
5858 *
5859 * @note Locks objects!
5860 */
5861HRESULT Machine::fixupHardDisks (bool aCommit)
5862{
5863 AutoCaller autoCaller (this);
5864 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
5865
5866 AutoLock alock (this);
5867
5868 /* no attac/detach operations -- nothing to do */
5869 if (!mHDData.isBackedUp())
5870 {
5871 mHDData->mHDAttachmentsChanged = false;
5872 return S_OK;
5873 }
5874
5875 AssertReturn (mData->mRegistered, E_FAIL);
5876
5877 if (aCommit)
5878 {
5879 /*
5880 * changes are being committed,
5881 * perform actual diff image creation, deletion etc.
5882 */
5883
5884 /* take a copy of backed up attachments (will modify it) */
5885 HDData::HDAttachmentList backedUp = mHDData.backedUpData()->mHDAttachments;
5886 /* list of new diffs created */
5887 std::list <ComObjPtr <HardDisk> > newDiffs;
5888
5889 HRESULT rc = S_OK;
5890
5891 /* go through current attachments */
5892 for (HDData::HDAttachmentList::const_iterator
5893 it = mHDData->mHDAttachments.begin();
5894 it != mHDData->mHDAttachments.end();
5895 ++ it)
5896 {
5897 ComObjPtr <HardDiskAttachment> hda = *it;
5898 ComObjPtr <HardDisk> hd = hda->hardDisk();
5899 AutoLock hdLock (hd);
5900
5901 if (!hda->isDirty())
5902 {
5903 /*
5904 * not dirty, therefore was either attached before backing up
5905 * or doesn't need any fixup (already fixed up); try to locate
5906 * this hard disk among backed up attachments and remove from
5907 * there to prevent it from being deassociated/deleted
5908 */
5909 HDData::HDAttachmentList::iterator oldIt;
5910 for (oldIt = backedUp.begin(); oldIt != backedUp.end(); ++ oldIt)
5911 if ((*oldIt)->hardDisk().equalsTo (hd))
5912 break;
5913 if (oldIt != backedUp.end())
5914 {
5915 /* remove from there */
5916 backedUp.erase (oldIt);
5917 // LogTraceMsg (("FC: %ls found in old\n", hd->toString().raw()));
5918 }
5919 }
5920 else
5921 {
5922 /* dirty, determine what to do */
5923
5924 bool needDiff = false;
5925 bool searchAmongSnapshots = false;
5926
5927 switch (hd->type())
5928 {
5929 case HardDiskType_ImmutableHardDisk:
5930 {
5931 /* decrease readers increased in AttachHardDisk() */
5932 hd->releaseReader();
5933 // LogTraceMsg (("FC: %ls released\n", hd->toString().raw()));
5934 /* indicate we need a diff (indirect attachment) */
5935 needDiff = true;
5936 break;
5937 }
5938 case HardDiskType_WritethroughHardDisk:
5939 {
5940 /* reset the dirty flag */
5941 hda->updateHardDisk (hd, false /* aDirty */);
5942 // LogTraceMsg (("FC: %ls updated\n", hd->toString().raw()));
5943 break;
5944 }
5945 case HardDiskType_NormalHardDisk:
5946 {
5947 if (hd->snapshotId().isEmpty())
5948 {
5949 /* reset the dirty flag */
5950 hda->updateHardDisk (hd, false /* aDirty */);
5951 // LogTraceMsg (("FC: %ls updated\n", hd->toString().raw()));
5952 }
5953 else
5954 {
5955 /* decrease readers increased in AttachHardDisk() */
5956 hd->releaseReader();
5957 // LogTraceMsg (("FC: %ls released\n", hd->toString().raw()));
5958 /* indicate we need a diff (indirect attachment) */
5959 needDiff = true;
5960 /* search for the most recent base among snapshots */
5961 searchAmongSnapshots = true;
5962 }
5963 break;
5964 }
5965 }
5966
5967 if (!needDiff)
5968 continue;
5969
5970 bool createDiff = false;
5971
5972 /*
5973 * see whether any previously attached hard disk has the
5974 * the currently attached one (Normal or Independent) as
5975 * the root
5976 */
5977
5978 HDData::HDAttachmentList::iterator foundIt = backedUp.end();
5979
5980 for (HDData::HDAttachmentList::iterator it = backedUp.begin();
5981 it != backedUp.end();
5982 ++ it)
5983 {
5984 if ((*it)->hardDisk()->root().equalsTo (hd))
5985 {
5986 /*
5987 * matched dev and ctl (i.e. attached to the same place)
5988 * will win and immediately stop the search; otherwise
5989 * the first attachment that matched the hd only will
5990 * be used
5991 */
5992 if ((*it)->deviceNumber() == hda->deviceNumber() &&
5993 (*it)->controller() == hda->controller())
5994 {
5995 foundIt = it;
5996 break;
5997 }
5998 else
5999 if (foundIt == backedUp.end())
6000 {
6001 /*
6002 * not an exact match; ensure there is no exact match
6003 * among other current attachments referring the same
6004 * root (to prevent this attachmend from reusing the
6005 * hard disk of the other attachment that will later
6006 * give the exact match or already gave it before)
6007 */
6008 bool canReuse = true;
6009 for (HDData::HDAttachmentList::const_iterator
6010 it2 = mHDData->mHDAttachments.begin();
6011 it2 != mHDData->mHDAttachments.end();
6012 ++ it2)
6013 {
6014 if ((*it2)->deviceNumber() == (*it)->deviceNumber() &&
6015 (*it2)->controller() == (*it)->controller() &&
6016 (*it2)->hardDisk()->root().equalsTo (hd))
6017 {
6018 /*
6019 * the exact match, either non-dirty or dirty
6020 * one refers the same root: in both cases
6021 * we cannot reuse the hard disk, so break
6022 */
6023 canReuse = false;
6024 break;
6025 }
6026 }
6027
6028 if (canReuse)
6029 foundIt = it;
6030 }
6031 }
6032 }
6033
6034 if (foundIt != backedUp.end())
6035 {
6036 /* found either one or another, reuse the diff */
6037 hda->updateHardDisk ((*foundIt)->hardDisk(),
6038 false /* aDirty */);
6039 // LogTraceMsg (("FC: %ls reused as %ls\n", hd->toString().raw(),
6040 // (*foundIt)->hardDisk()->toString().raw()));
6041 /* remove from there */
6042 backedUp.erase (foundIt);
6043 }
6044 else
6045 {
6046 /* was not attached, need a diff */
6047 createDiff = true;
6048 }
6049
6050 if (!createDiff)
6051 continue;
6052
6053 ComObjPtr <HardDisk> baseHd = hd;
6054
6055 if (searchAmongSnapshots)
6056 {
6057 /*
6058 * find the most recent diff based on the currently
6059 * attached root (Normal hard disk) among snapshots
6060 */
6061
6062 ComObjPtr <Snapshot> snap = mData->mCurrentSnapshot;
6063
6064 while (snap)
6065 {
6066 AutoLock snapLock (snap);
6067
6068 const HDData::HDAttachmentList &snapAtts =
6069 snap->data().mMachine->hdData()->mHDAttachments;
6070
6071 HDData::HDAttachmentList::const_iterator foundIt = snapAtts.end();
6072
6073 for (HDData::HDAttachmentList::const_iterator
6074 it = snapAtts.begin(); it != snapAtts.end(); ++ it)
6075 {
6076 if ((*it)->hardDisk()->root().equalsTo (hd))
6077 {
6078 /*
6079 * matched dev and ctl (i.e. attached to the same place)
6080 * will win and immediately stop the search; otherwise
6081 * the first attachment that matched the hd only will
6082 * be used
6083 */
6084 if ((*it)->deviceNumber() == hda->deviceNumber() &&
6085 (*it)->controller() == hda->controller())
6086 {
6087 foundIt = it;
6088 break;
6089 }
6090 else
6091 if (foundIt == snapAtts.end())
6092 foundIt = it;
6093 }
6094 }
6095
6096 if (foundIt != snapAtts.end())
6097 {
6098 /* the most recent diff has been found, use as a base */
6099 baseHd = (*foundIt)->hardDisk();
6100 // LogTraceMsg (("FC: %ls: recent found %ls\n",
6101 // hd->toString().raw(), baseHd->toString().raw()));
6102 break;
6103 }
6104
6105 snap = snap->parent();
6106 }
6107 }
6108
6109 /* create a new diff for the hard disk being indirectly attached */
6110
6111 AutoLock baseHdLock (baseHd);
6112 baseHd->addReader();
6113
6114 ComObjPtr <HVirtualDiskImage> vdi;
6115 rc = baseHd->createDiffHardDisk (mUserData->mSnapshotFolderFull,
6116 mData->mUuid, vdi, NULL);
6117 baseHd->releaseReader();
6118 CheckComRCBreakRC (rc);
6119
6120 newDiffs.push_back (ComObjPtr <HardDisk> (vdi));
6121
6122 /* update the attachment and reset the dirty flag */
6123 hda->updateHardDisk (ComObjPtr <HardDisk> (vdi),
6124 false /* aDirty */);
6125 // LogTraceMsg (("FC: %ls: diff created %ls\n",
6126 // baseHd->toString().raw(), vdi->toString().raw()));
6127 }
6128 }
6129
6130 if (FAILED (rc))
6131 {
6132 /* delete diffs we created */
6133 for (std::list <ComObjPtr <HardDisk> >::const_iterator
6134 it = newDiffs.begin(); it != newDiffs.end(); ++ it)
6135 {
6136 /*
6137 * unregisterDiffHardDisk() is supposed to delete and uninit
6138 * the differencing hard disk
6139 */
6140 mParent->unregisterDiffHardDisk (*it);
6141 /* too bad if we fail here, but nothing to do, just continue */
6142 }
6143
6144 /* the best is to rollback the changes... */
6145 mHDData.rollback();
6146 mHDData->mHDAttachmentsChanged = false;
6147 // LogTraceMsg (("FC: ROLLED BACK\n"));
6148 return rc;
6149 }
6150
6151 /*
6152 * go through the rest of old attachments and delete diffs
6153 * or deassociate hard disks from machines (they will become detached)
6154 */
6155 for (HDData::HDAttachmentList::iterator
6156 it = backedUp.begin(); it != backedUp.end(); ++ it)
6157 {
6158 ComObjPtr <HardDiskAttachment> hda = *it;
6159 ComObjPtr <HardDisk> hd = hda->hardDisk();
6160 AutoLock hdLock (hd);
6161
6162 if (hd->isDifferencing())
6163 {
6164 /*
6165 * unregisterDiffHardDisk() is supposed to delete and uninit
6166 * the differencing hard disk
6167 */
6168 // LogTraceMsg (("FC: %ls diff deleted\n", hd->toString().raw()));
6169 rc = mParent->unregisterDiffHardDisk (hd);
6170 /*
6171 * too bad if we fail here, but nothing to do, just continue
6172 * (the last rc will be returned to the caller though)
6173 */
6174 }
6175 else
6176 {
6177 /* deassociate from this machine */
6178 // LogTraceMsg (("FC: %ls deassociated\n", hd->toString().raw()));
6179 hd->setMachineId (Guid());
6180 }
6181 }
6182
6183 /* commit all the changes */
6184 mHDData->mHDAttachmentsChanged = mHDData.hasActualChanges();
6185 mHDData.commit();
6186 // LogTraceMsg (("FC: COMMITTED\n"));
6187
6188 return rc;
6189 }
6190
6191 /*
6192 * changes are being rolled back,
6193 * go trhough all current attachments and fix up dirty ones
6194 * the way it is done in DetachHardDisk()
6195 */
6196
6197 for (HDData::HDAttachmentList::iterator it = mHDData->mHDAttachments.begin();
6198 it != mHDData->mHDAttachments.end();
6199 ++ it)
6200 {
6201 ComObjPtr <HardDiskAttachment> hda = *it;
6202 ComObjPtr <HardDisk> hd = hda->hardDisk();
6203 AutoLock hdLock (hd);
6204
6205 if (hda->isDirty())
6206 {
6207 switch (hd->type())
6208 {
6209 case HardDiskType_ImmutableHardDisk:
6210 {
6211 /* decrease readers increased in AttachHardDisk() */
6212 hd->releaseReader();
6213 // LogTraceMsg (("FR: %ls released\n", hd->toString().raw()));
6214 break;
6215 }
6216 case HardDiskType_WritethroughHardDisk:
6217 {
6218 /* deassociate from this machine */
6219 hd->setMachineId (Guid());
6220 // LogTraceMsg (("FR: %ls deassociated\n", hd->toString().raw()));
6221 break;
6222 }
6223 case HardDiskType_NormalHardDisk:
6224 {
6225 if (hd->snapshotId().isEmpty())
6226 {
6227 /* deassociate from this machine */
6228 hd->setMachineId (Guid());
6229 // LogTraceMsg (("FR: %ls deassociated\n", hd->toString().raw()));
6230 }
6231 else
6232 {
6233 /* decrease readers increased in AttachHardDisk() */
6234 hd->releaseReader();
6235 // LogTraceMsg (("FR: %ls released\n", hd->toString().raw()));
6236 }
6237
6238 break;
6239 }
6240 }
6241 }
6242 }
6243
6244 /* rollback all the changes */
6245 mHDData.rollback();
6246 // LogTraceMsg (("FR: ROLLED BACK\n"));
6247
6248 return S_OK;
6249}
6250
6251/**
6252 * Creates differencing hard disks for all normal hard disks
6253 * and replaces attachments to refer to created disks.
6254 * Used when taking a snapshot or when discarding the current state.
6255 *
6256 * @param aSnapshotId ID of the snapshot being taken
6257 * or NULL if the current state is being discarded
6258 * @param aFolder folder where to create diff. hard disks
6259 * @param aProgress progress object to run (must contain at least as
6260 * many operations left as the number of VDIs attached)
6261 * @param aOnline whether the machine is online (i.e., when the EMT
6262 * thread is paused, OR when current hard disks are
6263 * marked as busy for some other reason)
6264 *
6265 * @note
6266 * The progress object is not marked as completed, neither on success
6267 * nor on failure. This is a responsibility of the caller.
6268 *
6269 * @note Locks mParent + this object for writing
6270 */
6271HRESULT Machine::createSnapshotDiffs (const Guid *aSnapshotId,
6272 const Bstr &aFolder,
6273 const ComObjPtr <Progress> &aProgress,
6274 bool aOnline)
6275{
6276 AssertReturn (!aFolder.isEmpty(), E_FAIL);
6277
6278 AutoCaller autoCaller (this);
6279 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
6280
6281 /* accessing mParent methods below needs mParent lock */
6282 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
6283
6284 HRESULT rc = S_OK;
6285
6286 // first pass: check accessibility before performing changes
6287 if (!aOnline)
6288 {
6289 for (HDData::HDAttachmentList::const_iterator it = mHDData->mHDAttachments.begin();
6290 it != mHDData->mHDAttachments.end();
6291 ++ it)
6292 {
6293 ComObjPtr <HardDiskAttachment> hda = *it;
6294 ComObjPtr <HardDisk> hd = hda->hardDisk();
6295 AutoLock hdLock (hd);
6296
6297 ComAssertMsgBreak (hd->type() == HardDiskType_NormalHardDisk,
6298 ("Invalid hard disk type %d\n", hd->type()),
6299 rc = E_FAIL);
6300
6301 ComAssertMsgBreak (!hd->isParentImmutable() ||
6302 hd->storageType() == HardDiskStorageType_VirtualDiskImage,
6303 ("Invalid hard disk storage type %d\n", hd->storageType()),
6304 rc = E_FAIL);
6305
6306 Bstr accessError;
6307 rc = hd->getAccessible (accessError);
6308 CheckComRCBreakRC (rc);
6309
6310 if (!accessError.isNull())
6311 {
6312 rc = setError (E_FAIL,
6313 tr ("Hard disk '%ls' is not accessible (%ls)"),
6314 hd->toString().raw(), accessError.raw());
6315 break;
6316 }
6317 }
6318 CheckComRCReturnRC (rc);
6319 }
6320
6321 HDData::HDAttachmentList attachments;
6322
6323 // second pass: perform changes
6324 for (HDData::HDAttachmentList::const_iterator it = mHDData->mHDAttachments.begin();
6325 it != mHDData->mHDAttachments.end();
6326 ++ it)
6327 {
6328 ComObjPtr <HardDiskAttachment> hda = *it;
6329 ComObjPtr <HardDisk> hd = hda->hardDisk();
6330 AutoLock hdLock (hd);
6331
6332 ComObjPtr <HardDisk> parent = hd->parent();
6333 AutoLock parentHdLock (parent);
6334
6335 ComObjPtr <HardDisk> newHd;
6336
6337 // clear busy flag if the VM is online
6338 if (aOnline)
6339 hd->clearBusy();
6340 // increase readers
6341 hd->addReader();
6342
6343 if (hd->isParentImmutable())
6344 {
6345 aProgress->advanceOperation (Bstr (Utf8StrFmt (
6346 tr ("Preserving immutable hard disk '%ls'"),
6347 parent->toString (true /* aShort */).raw())));
6348
6349 parentHdLock.unlock();
6350 alock.leave();
6351
6352 // create a copy of the independent diff
6353 ComObjPtr <HVirtualDiskImage> vdi;
6354 rc = hd->asVDI()->cloneDiffImage (aFolder, mData->mUuid, vdi,
6355 aProgress);
6356 newHd = vdi;
6357
6358 alock.enter();
6359 parentHdLock.lock();
6360
6361 // decrease readers (hd is no more used for reading in any case)
6362 hd->releaseReader();
6363 }
6364 else
6365 {
6366 // checked in the first pass
6367 Assert (hd->type() == HardDiskType_NormalHardDisk);
6368
6369 aProgress->advanceOperation (Bstr (Utf8StrFmt (
6370 tr ("Creating a differencing hard disk for '%ls'"),
6371 hd->root()->toString (true /* aShort */).raw())));
6372
6373 parentHdLock.unlock();
6374 alock.leave();
6375
6376 // create a new diff for the image being attached
6377 ComObjPtr <HVirtualDiskImage> vdi;
6378 rc = hd->createDiffHardDisk (aFolder, mData->mUuid, vdi, aProgress);
6379 newHd = vdi;
6380
6381 alock.enter();
6382 parentHdLock.lock();
6383
6384 if (SUCCEEDED (rc))
6385 {
6386 // if online, hd must keep a reader referece
6387 if (!aOnline)
6388 hd->releaseReader();
6389 }
6390 else
6391 {
6392 // decrease readers
6393 hd->releaseReader();
6394 }
6395 }
6396
6397 if (SUCCEEDED (rc))
6398 {
6399 ComObjPtr <HardDiskAttachment> newHda;
6400 newHda.createObject();
6401 rc = newHda->init (newHd, hda->controller(), hda->deviceNumber(),
6402 false /* aDirty */);
6403
6404 if (SUCCEEDED (rc))
6405 {
6406 // associate the snapshot id with the old hard disk
6407 if (hd->type() != HardDiskType_WritethroughHardDisk && aSnapshotId)
6408 hd->setSnapshotId (*aSnapshotId);
6409
6410 // add the new attachment
6411 attachments.push_back (newHda);
6412
6413 // if online, newHd must be marked as busy
6414 if (aOnline)
6415 newHd->setBusy();
6416 }
6417 }
6418
6419 if (FAILED (rc))
6420 {
6421 // set busy flag back if the VM is online
6422 if (aOnline)
6423 hd->setBusy();
6424 break;
6425 }
6426 }
6427
6428 if (SUCCEEDED (rc))
6429 {
6430 // replace the whole list of attachments with the new one
6431 mHDData->mHDAttachments = attachments;
6432 }
6433 else
6434 {
6435 // delete those diffs we've just created
6436 for (HDData::HDAttachmentList::const_iterator it = attachments.begin();
6437 it != attachments.end();
6438 ++ it)
6439 {
6440 ComObjPtr <HardDisk> hd = (*it)->hardDisk();
6441 AutoLock hdLock (hd);
6442 Assert (hd->children().size() == 0);
6443 Assert (hd->isDifferencing());
6444 // unregisterDiffHardDisk() is supposed to delete and uninit
6445 // the differencing hard disk
6446 mParent->unregisterDiffHardDisk (hd);
6447 }
6448 }
6449
6450 return rc;
6451}
6452
6453/**
6454 * Deletes differencing hard disks created by createSnapshotDiffs() in case
6455 * if snapshot creation was failed.
6456 *
6457 * @param aSnapshot failed snapshot
6458 *
6459 * @note Locks mParent + this object for writing.
6460 */
6461HRESULT Machine::deleteSnapshotDiffs (const ComObjPtr <Snapshot> &aSnapshot)
6462{
6463 AssertReturn (!aSnapshot.isNull(), E_FAIL);
6464
6465 AutoCaller autoCaller (this);
6466 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
6467
6468 /* accessing mParent methods below needs mParent lock */
6469 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
6470
6471 /* short cut: check whether attachments are all the same */
6472 if (mHDData->mHDAttachments == aSnapshot->data().mMachine->mHDData->mHDAttachments)
6473 return S_OK;
6474
6475 HRESULT rc = S_OK;
6476
6477 for (HDData::HDAttachmentList::const_iterator it = mHDData->mHDAttachments.begin();
6478 it != mHDData->mHDAttachments.end();
6479 ++ it)
6480 {
6481 ComObjPtr <HardDiskAttachment> hda = *it;
6482 ComObjPtr <HardDisk> hd = hda->hardDisk();
6483 AutoLock hdLock (hd);
6484
6485 ComObjPtr <HardDisk> parent = hd->parent();
6486 AutoLock parentHdLock (parent);
6487
6488 if (!parent || parent->snapshotId() != aSnapshot->data().mId)
6489 continue;
6490
6491 /* must not have children */
6492 ComAssertRet (hd->children().size() == 0, E_FAIL);
6493
6494 /* deassociate the old hard disk from the given snapshot's ID */
6495 parent->setSnapshotId (Guid());
6496
6497 /* unregisterDiffHardDisk() is supposed to delete and uninit
6498 * the differencing hard disk */
6499 rc = mParent->unregisterDiffHardDisk (hd);
6500 /* continue on error */
6501 }
6502
6503 /* restore the whole list of attachments from the failed snapshot */
6504 mHDData->mHDAttachments = aSnapshot->data().mMachine->mHDData->mHDAttachments;
6505
6506 return rc;
6507}
6508
6509/**
6510 * Helper to lock the machine configuration for write access.
6511 *
6512 * @return S_OK or E_FAIL and sets error info on failure
6513 *
6514 * @note Doesn't lock anything (must be called from this object's lock)
6515 */
6516HRESULT Machine::lockConfig()
6517{
6518 HRESULT rc = S_OK;
6519
6520 if (!isConfigLocked())
6521 {
6522 /* open the associated config file */
6523 int vrc = RTFileOpen (&mData->mHandleCfgFile,
6524 Utf8Str (mData->mConfigFileFull),
6525 RTFILE_O_READWRITE | RTFILE_O_OPEN |
6526 RTFILE_O_DENY_WRITE);
6527 if (VBOX_FAILURE (vrc))
6528 mData->mHandleCfgFile = NIL_RTFILE;
6529 }
6530
6531 LogFlowThisFunc (("mConfigFile={%ls}, mHandleCfgFile=%d, rc=%08X\n",
6532 mData->mConfigFileFull.raw(), mData->mHandleCfgFile, rc));
6533 return rc;
6534}
6535
6536/**
6537 * Helper to unlock the machine configuration from write access
6538 *
6539 * @return S_OK
6540 *
6541 * @note Doesn't lock anything.
6542 * @note Not thread safe (must be called from this object's lock).
6543 */
6544HRESULT Machine::unlockConfig()
6545{
6546 HRESULT rc = S_OK;
6547
6548 if (isConfigLocked())
6549 {
6550 RTFileClose(mData->mHandleCfgFile);
6551 mData->mHandleCfgFile = NIL_RTFILE;
6552 }
6553
6554 LogFlowThisFunc (("\n"));
6555
6556 return rc;
6557}
6558
6559/**
6560 * Returns true if the settings file is located in the directory named exactly
6561 * as the machine. This will be true if the machine settings structure was
6562 * created by default in #openConfigLoader().
6563 *
6564 * @param aSettingsDir if not NULL, the full machine settings file directory
6565 * name will be assigned there.
6566 *
6567 * @note Doesn't lock anything.
6568 * @note Not thread safe (must be called from this object's lock).
6569 */
6570bool Machine::isInOwnDir (Utf8Str *aSettingsDir /* = NULL */)
6571{
6572 Utf8Str settingsDir = mData->mConfigFileFull;
6573 RTPathStripFilename (settingsDir.mutableRaw());
6574 char *dirName = RTPathFilename (settingsDir);
6575
6576 AssertReturn (dirName, false);
6577
6578 /* if we don't rename anything on name change, return false shorlty */
6579 if (!mUserData->mNameSync)
6580 return false;
6581
6582 if (aSettingsDir)
6583 *aSettingsDir = settingsDir;
6584
6585 return Bstr (dirName) == mUserData->mName;
6586}
6587
6588/**
6589 * @note Locks objects for reading!
6590 */
6591bool Machine::isModified()
6592{
6593 AutoCaller autoCaller (this);
6594 AssertComRCReturn (autoCaller.rc(), false);
6595
6596 AutoReaderLock alock (this);
6597
6598 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
6599 if (mNetworkAdapters [slot] && mNetworkAdapters [slot]->isModified())
6600 return true;
6601
6602 return
6603 mUserData.isBackedUp() ||
6604 mHWData.isBackedUp() ||
6605 mHDData.isBackedUp() ||
6606#ifdef VBOX_VRDP
6607 (mVRDPServer && mVRDPServer->isModified()) ||
6608#endif
6609 (mDVDDrive && mDVDDrive->isModified()) ||
6610 (mFloppyDrive && mFloppyDrive->isModified()) ||
6611 (mAudioAdapter && mAudioAdapter->isModified()) ||
6612 (mUSBController && mUSBController->isModified()) ||
6613 (mBIOSSettings && mBIOSSettings->isModified());
6614}
6615
6616/**
6617 * @note This method doesn't check (ignores) actual changes to mHDData.
6618 * Use mHDData.mHDAttachmentsChanged right after #commit() instead.
6619 *
6620 * @param aIgnoreUserData |true| to ignore changes to mUserData
6621 *
6622 * @note Locks objects for reading!
6623 */
6624bool Machine::isReallyModified (bool aIgnoreUserData /* = false */)
6625{
6626 AutoCaller autoCaller (this);
6627 AssertComRCReturn (autoCaller.rc(), false);
6628
6629 AutoReaderLock alock (this);
6630
6631 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
6632 if (mNetworkAdapters [slot] && mNetworkAdapters [slot]->isReallyModified())
6633 return true;
6634
6635 return
6636 (!aIgnoreUserData && mUserData.hasActualChanges()) ||
6637 mHWData.hasActualChanges() ||
6638 /* ignore mHDData */
6639 //mHDData.hasActualChanges() ||
6640#ifdef VBOX_VRDP
6641 (mVRDPServer && mVRDPServer->isReallyModified()) ||
6642#endif
6643 (mDVDDrive && mDVDDrive->isReallyModified()) ||
6644 (mFloppyDrive && mFloppyDrive->isReallyModified()) ||
6645 (mAudioAdapter && mAudioAdapter->isReallyModified()) ||
6646 (mUSBController && mUSBController->isReallyModified()) ||
6647 (mBIOSSettings && mBIOSSettings->isReallyModified());
6648}
6649
6650/**
6651 * Discards all changes to machine settings.
6652 *
6653 * @param aNotify whether to notify the direct session about changes or not
6654 *
6655 * @note Locks objects!
6656 */
6657void Machine::rollback (bool aNotify)
6658{
6659 AutoCaller autoCaller (this);
6660 AssertComRCReturn (autoCaller.rc(), (void) 0);
6661
6662 AutoLock alock (this);
6663
6664 mUserData.rollback();
6665
6666 mHWData.rollback();
6667
6668 if (mHDData.isBackedUp())
6669 fixupHardDisks (false /* aCommit */);
6670
6671 bool vrdpChanged = false, dvdChanged = false, floppyChanged = false,
6672 usbChanged = false;
6673 ComPtr <INetworkAdapter> networkAdapters [ELEMENTS (mNetworkAdapters)];
6674
6675 if (mBIOSSettings)
6676 mBIOSSettings->rollback();
6677
6678#ifdef VBOX_VRDP
6679 if (mVRDPServer)
6680 vrdpChanged = mVRDPServer->rollback();
6681#endif
6682
6683 if (mDVDDrive)
6684 dvdChanged = mDVDDrive->rollback();
6685
6686 if (mFloppyDrive)
6687 floppyChanged = mFloppyDrive->rollback();
6688
6689 if (mAudioAdapter)
6690 mAudioAdapter->rollback();
6691
6692 if (mUSBController)
6693 usbChanged = mUSBController->rollback();
6694
6695 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
6696 if (mNetworkAdapters [slot])
6697 if (mNetworkAdapters [slot]->rollback())
6698 networkAdapters [slot] = mNetworkAdapters [slot];
6699
6700 if (aNotify)
6701 {
6702 // inform the direct session about changes
6703
6704 ComObjPtr <Machine> that = this;
6705 alock.leave();
6706
6707 if (vrdpChanged)
6708 that->onVRDPServerChange();
6709 if (dvdChanged)
6710 that->onDVDDriveChange();
6711 if (floppyChanged)
6712 that->onFloppyDriveChange();
6713 if (usbChanged)
6714 that->onUSBControllerChange();
6715 for (ULONG slot = 0; slot < ELEMENTS (networkAdapters); slot ++)
6716 if (networkAdapters [slot])
6717 that->onNetworkAdapterChange (networkAdapters [slot]);
6718 }
6719}
6720
6721/**
6722 * Commits all the changes to machine settings.
6723 *
6724 * Note that when committing fails at some stage, it still continues
6725 * until the end. So, all data will either be actually committed or rolled
6726 * back (for failed cases) and the returned result code will describe the
6727 * first failure encountered. However, #isModified() will still return true
6728 * in case of failure, to indicade that settings in memory and on disk are
6729 * out of sync.
6730 *
6731 * @note Locks objects!
6732 */
6733HRESULT Machine::commit()
6734{
6735 AutoCaller autoCaller (this);
6736 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
6737
6738 AutoLock alock (this);
6739
6740 HRESULT rc = S_OK;
6741
6742 /*
6743 * use safe commit to ensure Snapshot machines (that share mUserData)
6744 * will still refer to a valid memory location
6745 */
6746 mUserData.commitCopy();
6747
6748 mHWData.commit();
6749
6750 if (mHDData.isBackedUp())
6751 rc = fixupHardDisks (true /* aCommit */);
6752
6753 mBIOSSettings->commit();
6754#ifdef VBOX_VRDP
6755 mVRDPServer->commit();
6756#endif
6757 mDVDDrive->commit();
6758 mFloppyDrive->commit();
6759 mAudioAdapter->commit();
6760 mUSBController->commit();
6761
6762 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
6763 mNetworkAdapters [slot]->commit();
6764
6765 if (mType == IsSessionMachine)
6766 {
6767 /* attach new data to the primary machine and reshare it */
6768 mPeer->mUserData.attach (mUserData);
6769 mPeer->mHWData.attach (mHWData);
6770 mPeer->mHDData.attach (mHDData);
6771 }
6772
6773 if (FAILED (rc))
6774 {
6775 /*
6776 * backup arbitrary data item to cause #isModified() to still return
6777 * true in case of any error
6778 */
6779 mHWData.backup();
6780 }
6781
6782 return rc;
6783}
6784
6785/**
6786 * Copies all the hardware data from the given machine.
6787 *
6788 * @note
6789 * This method must be called from under this object's lock.
6790 * @note
6791 * This method doesn't call #commit(), so all data remains backed up
6792 * and unsaved.
6793 */
6794void Machine::copyFrom (Machine *aThat)
6795{
6796 AssertReturn (mType == IsMachine || mType == IsSessionMachine, (void) 0);
6797 AssertReturn (aThat->mType == IsSnapshotMachine, (void) 0);
6798
6799 mHWData.assignCopy (aThat->mHWData);
6800
6801 // create copies of all shared folders (mHWData after attiching a copy
6802 // contains just references to original objects)
6803 for (HWData::SharedFolderList::iterator it = mHWData->mSharedFolders.begin();
6804 it != mHWData->mSharedFolders.end();
6805 ++ it)
6806 {
6807 ComObjPtr <SharedFolder> folder;
6808 folder.createObject();
6809 HRESULT rc = folder->initCopy (machine(), *it);
6810 AssertComRC (rc);
6811 *it = folder;
6812 }
6813
6814 mBIOSSettings->copyFrom (aThat->mBIOSSettings);
6815#ifdef VBOX_VRDP
6816 mVRDPServer->copyFrom (aThat->mVRDPServer);
6817#endif
6818 mDVDDrive->copyFrom (aThat->mDVDDrive);
6819 mFloppyDrive->copyFrom (aThat->mFloppyDrive);
6820 mAudioAdapter->copyFrom (aThat->mAudioAdapter);
6821 mUSBController->copyFrom (aThat->mUSBController);
6822
6823 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
6824 mNetworkAdapters [slot]->copyFrom (aThat->mNetworkAdapters [slot]);
6825}
6826
6827/////////////////////////////////////////////////////////////////////////////
6828// SessionMachine class
6829/////////////////////////////////////////////////////////////////////////////
6830
6831/** Task structure for asynchronous VM operations */
6832struct SessionMachine::Task
6833{
6834 Task (SessionMachine *m, Progress *p)
6835 : machine (m), progress (p)
6836 , state (m->data()->mMachineState) // save the current machine state
6837 , subTask (false), settingsChanged (false)
6838 {}
6839
6840 void modifyLastState (MachineState_T s)
6841 {
6842 *const_cast <MachineState_T *> (&state) = s;
6843 }
6844
6845 virtual void handler() = 0;
6846
6847 const ComObjPtr <SessionMachine> machine;
6848 const ComObjPtr <Progress> progress;
6849 const MachineState_T state;
6850
6851 bool subTask : 1;
6852 bool settingsChanged : 1;
6853};
6854
6855/** Take snapshot task */
6856struct SessionMachine::TakeSnapshotTask : public SessionMachine::Task
6857{
6858 TakeSnapshotTask (SessionMachine *m)
6859 : Task (m, NULL) {}
6860
6861 void handler() { machine->takeSnapshotHandler (*this); }
6862};
6863
6864/** Discard snapshot task */
6865struct SessionMachine::DiscardSnapshotTask : public SessionMachine::Task
6866{
6867 DiscardSnapshotTask (SessionMachine *m, Progress *p, Snapshot *s)
6868 : Task (m, p)
6869 , snapshot (s) {}
6870
6871 DiscardSnapshotTask (const Task &task, Snapshot *s)
6872 : Task (task)
6873 , snapshot (s) {}
6874
6875 void handler() { machine->discardSnapshotHandler (*this); }
6876
6877 const ComObjPtr <Snapshot> snapshot;
6878};
6879
6880/** Discard current state task */
6881struct SessionMachine::DiscardCurrentStateTask : public SessionMachine::Task
6882{
6883 DiscardCurrentStateTask (SessionMachine *m, Progress *p,
6884 bool discardCurSnapshot)
6885 : Task (m, p), discardCurrentSnapshot (discardCurSnapshot) {}
6886
6887 void handler() { machine->discardCurrentStateHandler (*this); }
6888
6889 const bool discardCurrentSnapshot;
6890};
6891
6892////////////////////////////////////////////////////////////////////////////////
6893
6894DEFINE_EMPTY_CTOR_DTOR (SessionMachine)
6895
6896HRESULT SessionMachine::FinalConstruct()
6897{
6898 LogFlowThisFunc (("\n"));
6899
6900 /* set the proper type to indicate we're the SessionMachine instance */
6901 unconst (mType) = IsSessionMachine;
6902
6903#if defined(__WIN__)
6904 mIPCSem = NULL;
6905#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
6906 mIPCSem = -1;
6907#endif
6908
6909 return S_OK;
6910}
6911
6912void SessionMachine::FinalRelease()
6913{
6914 LogFlowThisFunc (("\n"));
6915
6916 uninit (Uninit::Unexpected);
6917}
6918
6919/**
6920 * @note Must be called only by Machine::openSession() from its own write lock.
6921 */
6922HRESULT SessionMachine::init (Machine *aMachine)
6923{
6924 LogFlowThisFuncEnter();
6925 LogFlowThisFunc (("mName={%ls}\n", aMachine->mUserData->mName.raw()));
6926
6927 AssertReturn (aMachine, E_INVALIDARG);
6928
6929 AssertReturn (aMachine->lockHandle()->isLockedOnCurrentThread(), E_FAIL);
6930
6931 /* Enclose the state transition NotReady->InInit->Ready */
6932 AutoInitSpan autoInitSpan (this);
6933 AssertReturn (autoInitSpan.isOk(), E_UNEXPECTED);
6934
6935 /* create the interprocess semaphore */
6936#if defined(__WIN__)
6937 mIPCSemName = aMachine->mData->mConfigFileFull;
6938 for (size_t i = 0; i < mIPCSemName.length(); i++)
6939 if (mIPCSemName[i] == '\\')
6940 mIPCSemName[i] = '/';
6941 mIPCSem = ::CreateMutex (NULL, FALSE, mIPCSemName);
6942 ComAssertMsgRet (mIPCSem, ("Cannot create IPC mutex, err=0x%08X", ::GetLastError()),
6943 E_FAIL);
6944#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
6945 Utf8Str configFile = aMachine->mData->mConfigFileFull;
6946 char *pszConfigFile = NULL;
6947 RTStrUtf8ToCurrentCP (&pszConfigFile, configFile);
6948 key_t key = ::ftok (pszConfigFile, 0);
6949 RTStrFree (pszConfigFile);
6950 mIPCSem = ::semget (key, 1, S_IRWXU | S_IRWXG | S_IRWXO | IPC_CREAT);
6951 ComAssertMsgRet (mIPCSem >= 0, ("Cannot create IPC semaphore, errno=%d", errno),
6952 E_FAIL);
6953 /* set the initial value to 1 */
6954 int rv = ::semctl (mIPCSem, 0, SETVAL, 1);
6955 ComAssertMsgRet (rv == 0, ("Cannot init IPC semaphore, errno=%d", errno),
6956 E_FAIL);
6957#endif
6958
6959 /* memorize the peer Machine */
6960 unconst (mPeer) = aMachine;
6961 /* share the parent pointer */
6962 unconst (mParent) = aMachine->mParent;
6963
6964 /* take the pointers to data to share */
6965 mData.share (aMachine->mData);
6966 mSSData.share (aMachine->mSSData);
6967
6968 mUserData.share (aMachine->mUserData);
6969 mHWData.share (aMachine->mHWData);
6970 mHDData.share (aMachine->mHDData);
6971
6972 unconst (mBIOSSettings).createObject();
6973 mBIOSSettings->init (this, aMachine->mBIOSSettings);
6974#ifdef VBOX_VRDP
6975 /* create another VRDPServer object that will be mutable */
6976 unconst (mVRDPServer).createObject();
6977 mVRDPServer->init (this, aMachine->mVRDPServer);
6978#endif
6979 /* create another DVD drive object that will be mutable */
6980 unconst (mDVDDrive).createObject();
6981 mDVDDrive->init (this, aMachine->mDVDDrive);
6982 /* create another floppy drive object that will be mutable */
6983 unconst (mFloppyDrive).createObject();
6984 mFloppyDrive->init (this, aMachine->mFloppyDrive);
6985 /* create another audio adapter object that will be mutable */
6986 unconst (mAudioAdapter).createObject();
6987 mAudioAdapter->init (this, aMachine->mAudioAdapter);
6988 /* create another USB controller object that will be mutable */
6989 unconst (mUSBController).createObject();
6990 mUSBController->init (this, aMachine->mUSBController);
6991 /* create a list of network adapters that will be mutable */
6992 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
6993 {
6994 unconst (mNetworkAdapters [slot]).createObject();
6995 mNetworkAdapters [slot]->init (this, aMachine->mNetworkAdapters [slot]);
6996 }
6997
6998 /* Confirm a successful initialization when it's the case */
6999 autoInitSpan.setSucceeded();
7000
7001 LogFlowThisFuncLeave();
7002 return S_OK;
7003}
7004
7005/**
7006 * Uninitializes this session object. If the reason is other than
7007 * Uninit::Unexpected, then this method MUST be called from #checkForDeath().
7008 *
7009 * @param aReason uninitialization reason
7010 *
7011 * @note Locks mParent + this object for writing.
7012 */
7013void SessionMachine::uninit (Uninit::Reason aReason)
7014{
7015 LogFlowThisFuncEnter();
7016 LogFlowThisFunc (("reason=%d\n", aReason));
7017
7018 /*
7019 * Strongly reference ourselves to prevent this object deletion after
7020 * mData->mSession.mMachine.setNull() below (which can release the last
7021 * reference and call the destructor). Important: this must be done before
7022 * accessing any members (and before AutoUninitSpan that does it as well).
7023 * This self reference will be released as the very last step on return.
7024 */
7025 ComObjPtr <SessionMachine> selfRef = this;
7026
7027 /* Enclose the state transition Ready->InUninit->NotReady */
7028 AutoUninitSpan autoUninitSpan (this);
7029 if (autoUninitSpan.uninitDone())
7030 {
7031 LogFlowThisFunc (("Already uninitialized\n"));
7032 LogFlowThisFuncLeave();
7033 return;
7034 }
7035
7036 if (autoUninitSpan.initFailed())
7037 {
7038 /*
7039 * We've been called by init() because it's failed. It's not really
7040 * necessary (nor it's safe) to perform the regular uninit sequence
7041 * below, the following is enough.
7042 */
7043 LogFlowThisFunc (("Initialization failed\n"));
7044#if defined(__WIN__)
7045 if (mIPCSem)
7046 ::CloseHandle (mIPCSem);
7047 mIPCSem = NULL;
7048#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
7049 if (mIPCSem >= 0)
7050 ::semctl (mIPCSem, 0, IPC_RMID);
7051 mIPCSem = -1;
7052#endif
7053 uninitDataAndChildObjects();
7054 unconst (mParent).setNull();
7055 unconst (mPeer).setNull();
7056 LogFlowThisFuncLeave();
7057 return;
7058 }
7059
7060 /*
7061 * We need to lock this object in uninit() because the lock is shared
7062 * with mPeer (as well as data we modify below).
7063 * mParent->addProcessToReap() and others need mParent lock.
7064 */
7065 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
7066
7067 if (isModified())
7068 {
7069 LogWarningThisFunc (("Discarding unsaved settings changes!\n"));
7070 rollback (false /* aNotify */);
7071 }
7072
7073 Assert (!mSnapshotData.mStateFilePath || !mSnapshotData.mSnapshot);
7074 if (mSnapshotData.mStateFilePath)
7075 {
7076 LogWarningThisFunc (("canceling failed save state request!\n"));
7077 endSavingState (FALSE /* aSuccess */);
7078 }
7079 else if (!!mSnapshotData.mSnapshot)
7080 {
7081 LogWarningThisFunc (("canceling untaken snapshot!\n"));
7082 endTakingSnapshot (FALSE /* aSuccess */);
7083 }
7084
7085 /* release all captured USB devices */
7086 mParent->host()->releaseAllUSBDevices (this);
7087
7088 if (mData->mSession.mPid != NIL_RTPROCESS)
7089 {
7090 /*
7091 * pid is not NIL, meaning this machine's process has been started
7092 * using VirtualBox::OpenRemoteSession(), thus it is our child.
7093 * we need to queue this pid to be reaped (to avoid zombies on Linux)
7094 */
7095 mParent->addProcessToReap (mData->mSession.mPid);
7096 mData->mSession.mPid = NIL_RTPROCESS;
7097 }
7098
7099 if (aReason == Uninit::Unexpected)
7100 {
7101 /*
7102 * uninitialization didn't come from #checkForDeath(), so tell the
7103 * client watcher thread to update the set of machines that have
7104 * open sessions.
7105 */
7106 mParent->updateClientWatcher();
7107 }
7108
7109 /* uninitialize all remote controls */
7110 if (mData->mSession.mRemoteControls.size())
7111 {
7112 LogFlowThisFunc (("Closing remote sessions (%d):\n",
7113 mData->mSession.mRemoteControls.size()));
7114
7115 Data::Session::RemoteControlList::iterator it =
7116 mData->mSession.mRemoteControls.begin();
7117 while (it != mData->mSession.mRemoteControls.end())
7118 {
7119 LogFlowThisFunc ((" Calling remoteControl->Uninitialize()...\n"));
7120 HRESULT rc = (*it)->Uninitialize();
7121 LogFlowThisFunc ((" remoteControl->Uninitialize() returned %08X\n", rc));
7122 if (FAILED (rc))
7123 LogWarningThisFunc (("Forgot to close the remote session?\n"));
7124 ++ it;
7125 }
7126 mData->mSession.mRemoteControls.clear();
7127 }
7128
7129 /*
7130 * An expected uninitialization can come only from #checkForDeath().
7131 * Otherwise it means that something's got really wrong (for examlple,
7132 * the Session implementation has released the VirtualBox reference
7133 * before it triggered #OnSessionEnd(), or before releasing IPC semaphore,
7134 * etc). However, it's also possible, that the client releases the IPC
7135 * semaphore correctly (i.e. before it releases the VirtualBox reference),
7136 * but but the VirtualBox release event comes first to the server process.
7137 * This case is practically possible, so we should not assert on an
7138 * unexpected uninit, just log a warning.
7139 */
7140
7141 if ((aReason == Uninit::Unexpected))
7142 LogWarningThisFunc (("Unexpected SessionMachine uninitialization!\n"));
7143
7144 if (aReason != Uninit::Normal)
7145 mData->mSession.mDirectControl.setNull();
7146 else
7147 {
7148 /* this must be null here (see #OnSessionEnd()) */
7149 Assert (mData->mSession.mDirectControl.isNull());
7150 Assert (mData->mSession.mState == SessionState_SessionClosing);
7151 Assert (!mData->mSession.mProgress.isNull());
7152
7153 mData->mSession.mProgress->notifyComplete (S_OK);
7154 mData->mSession.mProgress.setNull();
7155 }
7156
7157 /* remove the association between the peer machine and this session machine */
7158 Assert (mData->mSession.mMachine == this ||
7159 aReason == Uninit::Unexpected);
7160
7161 /* reset the rest of session data */
7162 mData->mSession.mMachine.setNull();
7163 mData->mSession.mState = SessionState_SessionClosed;
7164
7165 /* close the interprocess semaphore before leaving the shared lock */
7166#if defined(__WIN__)
7167 if (mIPCSem)
7168 ::CloseHandle (mIPCSem);
7169 mIPCSem = NULL;
7170#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
7171 if (mIPCSem >= 0)
7172 ::semctl (mIPCSem, 0, IPC_RMID);
7173 mIPCSem = -1;
7174#endif
7175
7176 /* fire an event */
7177 mParent->onSessionStateChange (mData->mUuid, SessionState_SessionClosed);
7178
7179 uninitDataAndChildObjects();
7180
7181 /* leave the shared lock before setting the above two to NULL */
7182 alock.leave();
7183
7184 unconst (mParent).setNull();
7185 unconst (mPeer).setNull();
7186
7187 LogFlowThisFuncLeave();
7188}
7189
7190// AutoLock::Lockable interface
7191////////////////////////////////////////////////////////////////////////////////
7192
7193/**
7194 * Overrides VirtualBoxBase::lockHandle() in order to share the lock handle
7195 * with the primary Machine instance (mPeer).
7196 */
7197AutoLock::Handle *SessionMachine::lockHandle() const
7198{
7199 AssertReturn (!mPeer.isNull(), NULL);
7200 return mPeer->lockHandle();
7201}
7202
7203// IInternalMachineControl methods
7204////////////////////////////////////////////////////////////////////////////////
7205
7206/**
7207 * @note Locks the same as #setMachineState() does.
7208 */
7209STDMETHODIMP SessionMachine::UpdateState (MachineState_T machineState)
7210{
7211 return setMachineState (machineState);
7212}
7213
7214/**
7215 * @note Locks this object for reading.
7216 */
7217STDMETHODIMP SessionMachine::GetIPCId (BSTR *id)
7218{
7219 AutoCaller autoCaller (this);
7220 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7221
7222 AutoReaderLock alock (this);
7223
7224#if defined(__WIN__)
7225 mIPCSemName.cloneTo (id);
7226 return S_OK;
7227#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
7228 mData->mConfigFileFull.cloneTo (id);
7229 return S_OK;
7230#else
7231 return S_FAIL;
7232#endif
7233}
7234
7235/**
7236 * @note Locks this object for reading.
7237 */
7238STDMETHODIMP SessionMachine::GetLogFolder (BSTR *aLogFolder)
7239{
7240 AutoCaller autoCaller (this);
7241 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7242
7243 AutoReaderLock alock (this);
7244
7245 Utf8Str logFolder;
7246 getLogFolder (logFolder);
7247
7248 Bstr (logFolder).cloneTo (aLogFolder);
7249
7250 return S_OK;
7251}
7252
7253/**
7254 * Goes through the USB filters of the given machine to see if the given
7255 * device matches any filter or not.
7256 *
7257 * @note Locks the same as USBController::hasMatchingFilter() does.
7258 */
7259STDMETHODIMP SessionMachine::RunUSBDeviceFilters (IUSBDevice *aUSBDevice,
7260 BOOL *aMatched)
7261{
7262 if (!aUSBDevice)
7263 return E_INVALIDARG;
7264 if (!aMatched)
7265 return E_POINTER;
7266
7267 AutoCaller autoCaller (this);
7268 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7269
7270 *aMatched = mUSBController->hasMatchingFilter (aUSBDevice);
7271
7272 return S_OK;
7273}
7274
7275/**
7276 * @note Locks the same as Host::captureUSBDevice() does.
7277 */
7278STDMETHODIMP SessionMachine::CaptureUSBDevice (INPTR GUIDPARAM aId,
7279 IUSBDevice **aHostDevice)
7280{
7281 if (!aHostDevice)
7282 return E_POINTER;
7283
7284 AutoCaller autoCaller (this);
7285 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7286
7287 // if cautureUSBDevice() fails, it must have set extended error info
7288 return mParent->host()->captureUSBDevice (this, aId, aHostDevice);
7289}
7290
7291/**
7292 * @note Locks the same as Host::releaseUSBDevice() does.
7293 */
7294STDMETHODIMP SessionMachine::ReleaseUSBDevice (INPTR GUIDPARAM aId)
7295{
7296 AutoCaller autoCaller (this);
7297 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7298
7299 return mParent->host()->releaseUSBDevice (this, aId);
7300}
7301
7302/**
7303 * @note Locks the same as Host::autoCaptureUSBDevices() does.
7304 */
7305STDMETHODIMP SessionMachine::AutoCaptureUSBDevices (IUSBDeviceCollection **aHostDevices)
7306{
7307 AutoCaller autoCaller (this);
7308 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7309
7310 return mParent->host()->autoCaptureUSBDevices (this, aHostDevices);
7311}
7312
7313/**
7314 * @note Locks the same as Host::releaseAllUSBDevices() does.
7315 */
7316STDMETHODIMP SessionMachine::ReleaseAllUSBDevices()
7317{
7318 AutoCaller autoCaller (this);
7319 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7320
7321 return mParent->host()->releaseAllUSBDevices (this);
7322}
7323
7324/**
7325 * @note Locks mParent + this object for writing.
7326 */
7327STDMETHODIMP SessionMachine::OnSessionEnd (ISession *aSession,
7328 IProgress **aProgress)
7329{
7330 LogFlowThisFuncEnter();
7331
7332 AssertReturn (aSession, E_INVALIDARG);
7333 AssertReturn (aProgress, E_INVALIDARG);
7334
7335 AutoCaller autoCaller (this);
7336
7337 LogFlowThisFunc (("state=%d\n", autoCaller.state()));
7338 /*
7339 * We don't assert below because it might happen that a non-direct session
7340 * informs us it is closed right after we've been uninitialized -- it's ok.
7341 */
7342 CheckComRCReturnRC (autoCaller.rc());
7343
7344 /* get IInternalSessionControl interface */
7345 ComPtr <IInternalSessionControl> control (aSession);
7346
7347 ComAssertRet (!control.isNull(), E_INVALIDARG);
7348
7349 /* Progress::init() needs mParent lock */
7350 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
7351
7352 if (control.equalsTo (mData->mSession.mDirectControl))
7353 {
7354 ComAssertRet (aProgress, E_POINTER);
7355
7356 /* The direct session is being normally closed by the client process
7357 * ----------------------------------------------------------------- */
7358
7359 /* go to the closing state (essential for all open*Session() calls and
7360 * for #checkForDeath()) */
7361 Assert (mData->mSession.mState == SessionState_SessionOpen);
7362 mData->mSession.mState = SessionState_SessionClosing;
7363
7364 /* set direct control to NULL to release the remote instance */
7365 mData->mSession.mDirectControl.setNull();
7366 LogFlowThisFunc (("Direct control is set to NULL\n"));
7367
7368 /*
7369 * Create the progress object the client will use to wait until
7370 * #checkForDeath() is called to uninitialize this session object
7371 * after it releases the IPC semaphore.
7372 */
7373 ComObjPtr <Progress> progress;
7374 progress.createObject();
7375 progress->init (mParent, (IMachine *) mPeer, Bstr (tr ("Closing session")),
7376 FALSE /* aCancelable */);
7377 progress.queryInterfaceTo (aProgress);
7378 mData->mSession.mProgress = progress;
7379 }
7380 else
7381 {
7382 /* the remote session is being normally closed */
7383 Data::Session::RemoteControlList::iterator it =
7384 mData->mSession.mRemoteControls.begin();
7385 while (it != mData->mSession.mRemoteControls.end())
7386 {
7387 if (control.equalsTo (*it))
7388 break;
7389 ++it;
7390 }
7391 BOOL found = it != mData->mSession.mRemoteControls.end();
7392 ComAssertMsgRet (found, ("The session is not found in the session list!"),
7393 E_INVALIDARG);
7394 mData->mSession.mRemoteControls.remove (*it);
7395 }
7396
7397 LogFlowThisFuncLeave();
7398 return S_OK;
7399}
7400
7401/**
7402 * @note Locks mParent + this object for writing.
7403 */
7404STDMETHODIMP SessionMachine::BeginSavingState (IProgress *aProgress, BSTR *aStateFilePath)
7405{
7406 LogFlowThisFuncEnter();
7407
7408 AssertReturn (aProgress, E_INVALIDARG);
7409 AssertReturn (aStateFilePath, E_POINTER);
7410
7411 AutoCaller autoCaller (this);
7412 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7413
7414 /* mParent->addProgress() needs mParent lock */
7415 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
7416
7417 AssertReturn (mData->mMachineState == MachineState_Paused &&
7418 mSnapshotData.mLastState == MachineState_InvalidMachineState &&
7419 mSnapshotData.mProgressId.isEmpty() &&
7420 mSnapshotData.mStateFilePath.isNull(),
7421 E_FAIL);
7422
7423 /* memorize the progress ID and add it to the global collection */
7424 Guid progressId;
7425 HRESULT rc = aProgress->COMGETTER(Id) (progressId.asOutParam());
7426 AssertComRCReturn (rc, rc);
7427 rc = mParent->addProgress (aProgress);
7428 AssertComRCReturn (rc, rc);
7429
7430 Bstr stateFilePath;
7431 /* stateFilePath is null when the machine is not running */
7432 if (mData->mMachineState == MachineState_Paused)
7433 {
7434 stateFilePath = Utf8StrFmt ("%ls%c{%Vuuid}.sav",
7435 mUserData->mSnapshotFolderFull.raw(),
7436 RTPATH_DELIMITER, mData->mUuid.raw());
7437 }
7438
7439 /* fill in the snapshot data */
7440 mSnapshotData.mLastState = mData->mMachineState;
7441 mSnapshotData.mProgressId = progressId;
7442 mSnapshotData.mStateFilePath = stateFilePath;
7443
7444 /* set the state to Saving (this is expected by Console::SaveState()) */
7445 setMachineState (MachineState_Saving);
7446
7447 stateFilePath.cloneTo (aStateFilePath);
7448
7449 return S_OK;
7450}
7451
7452/**
7453 * @note Locks mParent + this objects for writing.
7454 */
7455STDMETHODIMP SessionMachine::EndSavingState (BOOL aSuccess)
7456{
7457 LogFlowThisFunc (("\n"));
7458
7459 AutoCaller autoCaller (this);
7460 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7461
7462 /* endSavingState() need mParent lock */
7463 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
7464
7465 AssertReturn (mData->mMachineState == MachineState_Saving &&
7466 mSnapshotData.mLastState != MachineState_InvalidMachineState &&
7467 !mSnapshotData.mProgressId.isEmpty() &&
7468 !mSnapshotData.mStateFilePath.isNull(),
7469 E_FAIL);
7470
7471 /*
7472 * on success, set the state to Saved;
7473 * on failure, set the state to the state we had when BeginSavingState() was
7474 * called (this is expected by Console::SaveState() and
7475 * Console::saveStateThread())
7476 */
7477 if (aSuccess)
7478 setMachineState (MachineState_Saved);
7479 else
7480 setMachineState (mSnapshotData.mLastState);
7481
7482 return endSavingState (aSuccess);
7483}
7484
7485/**
7486 * @note Locks mParent + this objects for writing.
7487 */
7488STDMETHODIMP SessionMachine::BeginTakingSnapshot (
7489 IConsole *aInitiator, INPTR BSTR aName, INPTR BSTR aDescription,
7490 IProgress *aProgress, BSTR *aStateFilePath,
7491 IProgress **aServerProgress)
7492{
7493 LogFlowThisFuncEnter();
7494
7495 AssertReturn (aInitiator && aName, E_INVALIDARG);
7496 AssertReturn (aStateFilePath && aServerProgress, E_POINTER);
7497
7498 LogFlowThisFunc (("aName='%ls'\n", aName));
7499
7500 AutoCaller autoCaller (this);
7501 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7502
7503 /* Progress::init() needs mParent lock */
7504 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
7505
7506 AssertReturn ((mData->mMachineState < MachineState_Running ||
7507 mData->mMachineState == MachineState_Paused) &&
7508 mSnapshotData.mLastState == MachineState_InvalidMachineState &&
7509 mSnapshotData.mSnapshot.isNull() &&
7510 mSnapshotData.mServerProgress.isNull() &&
7511 mSnapshotData.mCombinedProgress.isNull(),
7512 E_FAIL);
7513
7514 bool takingSnapshotOnline = mData->mMachineState == MachineState_Paused;
7515
7516 if (!takingSnapshotOnline && mData->mMachineState != MachineState_Saved)
7517 {
7518 /*
7519 * save all current settings to ensure current changes are committed
7520 * and hard disks are fixed up
7521 */
7522 HRESULT rc = saveSettings();
7523 CheckComRCReturnRC (rc);
7524 }
7525
7526 /* check that there are no Writethrough hard disks attached */
7527 for (HDData::HDAttachmentList::const_iterator
7528 it = mHDData->mHDAttachments.begin();
7529 it != mHDData->mHDAttachments.end();
7530 ++ it)
7531 {
7532 ComObjPtr <HardDisk> hd = (*it)->hardDisk();
7533 AutoLock hdLock (hd);
7534 if (hd->type() == HardDiskType_WritethroughHardDisk)
7535 return setError (E_FAIL,
7536 tr ("Cannot take a snapshot when there is a Writethrough hard "
7537 " disk attached ('%ls')"), hd->toString().raw());
7538 }
7539
7540 AssertReturn (aProgress || !takingSnapshotOnline, E_FAIL);
7541
7542 /* create an ID for the snapshot */
7543 Guid snapshotId;
7544 snapshotId.create();
7545
7546 Bstr stateFilePath;
7547 /* stateFilePath is null when the machine is not online nor saved */
7548 if (takingSnapshotOnline || mData->mMachineState == MachineState_Saved)
7549 stateFilePath = Utf8StrFmt ("%ls%c{%Vuuid}.sav",
7550 mUserData->mSnapshotFolderFull.raw(),
7551 RTPATH_DELIMITER,
7552 snapshotId.ptr());
7553
7554 /* ensure the directory for the saved state file exists */
7555 if (stateFilePath)
7556 {
7557 Utf8Str dir = stateFilePath;
7558 RTPathStripFilename (dir.mutableRaw());
7559 if (!RTDirExists (dir))
7560 {
7561 int vrc = RTDirCreateFullPath (dir, 0777);
7562 if (VBOX_FAILURE (vrc))
7563 return setError (E_FAIL,
7564 tr ("Could not create a directory '%s' to save the "
7565 "VM state to (%Vrc)"),
7566 dir.raw(), vrc);
7567 }
7568 }
7569
7570 /* create a snapshot machine object */
7571 ComObjPtr <SnapshotMachine> snapshotMachine;
7572 snapshotMachine.createObject();
7573 HRESULT rc = snapshotMachine->init (this, snapshotId, stateFilePath);
7574 AssertComRCReturn (rc, rc);
7575
7576 Bstr progressDesc = Bstr (tr ("Taking snapshot of virtual machine"));
7577 Bstr firstOpDesc = Bstr (tr ("Preparing to take snapshot"));
7578
7579 /*
7580 * create a server-side progress object (it will be descriptionless
7581 * when we need to combine it with the VM-side progress, i.e. when we're
7582 * taking a snapshot online). The number of operations is:
7583 * 1 (preparing) + # of VDIs + 1 (if the state is saved so we need to copy it)
7584 */
7585 ComObjPtr <Progress> serverProgress;
7586 {
7587 ULONG opCount = 1 + mHDData->mHDAttachments.size();
7588 if (mData->mMachineState == MachineState_Saved)
7589 opCount ++;
7590 serverProgress.createObject();
7591 if (takingSnapshotOnline)
7592 rc = serverProgress->init (FALSE, opCount, firstOpDesc);
7593 else
7594 rc = serverProgress->init (mParent, aInitiator, progressDesc, FALSE,
7595 opCount, firstOpDesc);
7596 AssertComRCReturn (rc, rc);
7597 }
7598
7599 /* create a combined server-side progress object when necessary */
7600 ComObjPtr <CombinedProgress> combinedProgress;
7601 if (takingSnapshotOnline)
7602 {
7603 combinedProgress.createObject();
7604 rc = combinedProgress->init (mParent, aInitiator, progressDesc,
7605 serverProgress, aProgress);
7606 AssertComRCReturn (rc, rc);
7607 }
7608
7609 /* create a snapshot object */
7610 RTTIMESPEC time;
7611 ComObjPtr <Snapshot> snapshot;
7612 snapshot.createObject();
7613 rc = snapshot->init (snapshotId, aName, aDescription,
7614 RTTimeSpecGetMilli (RTTimeNow (&time)),
7615 snapshotMachine, mData->mCurrentSnapshot);
7616 AssertComRCReturn (rc, rc);
7617
7618 /*
7619 * create and start the task on a separate thread
7620 * (note that it will not start working until we release alock)
7621 */
7622 TakeSnapshotTask *task = new TakeSnapshotTask (this);
7623 int vrc = RTThreadCreate (NULL, taskHandler,
7624 (void *) task,
7625 0, RTTHREADTYPE_MAIN_WORKER, 0, "TakeSnapshot");
7626 if (VBOX_FAILURE (vrc))
7627 {
7628 snapshot->uninit();
7629 delete task;
7630 ComAssertFailedRet (E_FAIL);
7631 }
7632
7633 /* fill in the snapshot data */
7634 mSnapshotData.mLastState = mData->mMachineState;
7635 mSnapshotData.mSnapshot = snapshot;
7636 mSnapshotData.mServerProgress = serverProgress;
7637 mSnapshotData.mCombinedProgress = combinedProgress;
7638
7639 /* set the state to Saving (this is expected by Console::TakeSnapshot()) */
7640 setMachineState (MachineState_Saving);
7641
7642 if (takingSnapshotOnline)
7643 stateFilePath.cloneTo (aStateFilePath);
7644 else
7645 *aStateFilePath = NULL;
7646
7647 serverProgress.queryInterfaceTo (aServerProgress);
7648
7649 LogFlowThisFuncLeave();
7650 return S_OK;
7651}
7652
7653/**
7654 * @note Locks mParent + this objects for writing.
7655 */
7656STDMETHODIMP SessionMachine::EndTakingSnapshot (BOOL aSuccess)
7657{
7658 LogFlowThisFunc (("\n"));
7659
7660 AutoCaller autoCaller (this);
7661 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7662
7663 /* Lock mParent because of endTakingSnapshot() */
7664 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
7665
7666 AssertReturn (!aSuccess ||
7667 (mData->mMachineState == MachineState_Saving &&
7668 mSnapshotData.mLastState != MachineState_InvalidMachineState &&
7669 !mSnapshotData.mSnapshot.isNull() &&
7670 !mSnapshotData.mServerProgress.isNull() &&
7671 !mSnapshotData.mCombinedProgress.isNull()),
7672 E_FAIL);
7673
7674 /*
7675 * set the state to the state we had when BeginTakingSnapshot() was called
7676 * (this is expected by Console::TakeSnapshot() and
7677 * Console::saveStateThread())
7678 */
7679 setMachineState (mSnapshotData.mLastState);
7680
7681 return endTakingSnapshot (aSuccess);
7682}
7683
7684/**
7685 * @note Locks mParent + this + children objects for writing!
7686 */
7687STDMETHODIMP SessionMachine::DiscardSnapshot (
7688 IConsole *aInitiator, INPTR GUIDPARAM aId,
7689 MachineState_T *aMachineState, IProgress **aProgress)
7690{
7691 LogFlowThisFunc (("\n"));
7692
7693 Guid id = aId;
7694 AssertReturn (aInitiator && !id.isEmpty(), E_INVALIDARG);
7695 AssertReturn (aMachineState && aProgress, E_POINTER);
7696
7697 AutoCaller autoCaller (this);
7698 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7699
7700 /* Progress::init() needs mParent lock */
7701 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
7702
7703 ComAssertRet (mData->mMachineState < MachineState_Running, E_FAIL);
7704
7705 ComObjPtr <Snapshot> snapshot;
7706 HRESULT rc = findSnapshot (id, snapshot, true /* aSetError */);
7707 CheckComRCReturnRC (rc);
7708
7709 AutoLock snapshotLock (snapshot);
7710 if (snapshot == mData->mFirstSnapshot)
7711 {
7712 AutoLock chLock (mData->mFirstSnapshot->childrenLock());
7713 size_t childrenCount = mData->mFirstSnapshot->children().size();
7714 if (childrenCount > 1)
7715 return setError (E_FAIL,
7716 tr ("Cannot discard the snapshot '%ls' because it is the first "
7717 "snapshot of the machine '%ls' and it has more than one "
7718 "child snapshot (%d)"),
7719 snapshot->data().mName.raw(), mUserData->mName.raw(),
7720 childrenCount);
7721 }
7722
7723 /*
7724 * If the snapshot being discarded is the current one, ensure current
7725 * settings are committed and saved.
7726 */
7727 if (snapshot == mData->mCurrentSnapshot)
7728 {
7729 if (isModified())
7730 {
7731 rc = saveSettings();
7732 CheckComRCReturnRC (rc);
7733 }
7734 }
7735
7736 /*
7737 * create a progress object. The number of operations is:
7738 * 1 (preparing) + # of VDIs
7739 */
7740 ComObjPtr <Progress> progress;
7741 progress.createObject();
7742 rc = progress->init (mParent, aInitiator,
7743 Bstr (Utf8StrFmt (tr ("Discarding snapshot '%ls'"),
7744 snapshot->data().mName.raw())),
7745 FALSE /* aCancelable */,
7746 1 + snapshot->data().mMachine->mHDData->mHDAttachments.size(),
7747 Bstr (tr ("Preparing to discard snapshot")));
7748 AssertComRCReturn (rc, rc);
7749
7750 /* create and start the task on a separate thread */
7751 DiscardSnapshotTask *task = new DiscardSnapshotTask (this, progress, snapshot);
7752 int vrc = RTThreadCreate (NULL, taskHandler,
7753 (void *) task,
7754 0, RTTHREADTYPE_MAIN_WORKER, 0, "DiscardSnapshot");
7755 if (VBOX_FAILURE (vrc))
7756 delete task;
7757 ComAssertRCRet (vrc, E_FAIL);
7758
7759 /* set the proper machine state (note: after creating a Task instance) */
7760 setMachineState (MachineState_Discarding);
7761
7762 /* return the progress to the caller */
7763 progress.queryInterfaceTo (aProgress);
7764
7765 /* return the new state to the caller */
7766 *aMachineState = mData->mMachineState;
7767
7768 return S_OK;
7769}
7770
7771/**
7772 * @note Locks mParent + this + children objects for writing!
7773 */
7774STDMETHODIMP SessionMachine::DiscardCurrentState (
7775 IConsole *aInitiator, MachineState_T *aMachineState, IProgress **aProgress)
7776{
7777 LogFlowThisFunc (("\n"));
7778
7779 AssertReturn (aInitiator, E_INVALIDARG);
7780 AssertReturn (aMachineState && aProgress, E_POINTER);
7781
7782 AutoCaller autoCaller (this);
7783 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7784
7785 /* Progress::init() needs mParent lock */
7786 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
7787
7788 ComAssertRet (mData->mMachineState < MachineState_Running, E_FAIL);
7789
7790 if (mData->mCurrentSnapshot.isNull())
7791 return setError (E_FAIL,
7792 tr ("Could not discard the current state of the machine '%ls' "
7793 "because it doesn't have any snapshots"),
7794 mUserData->mName.raw());
7795
7796 /*
7797 * create a progress object. The number of operations is:
7798 * 1 (preparing) + # of VDIs + 1 (if we need to copy the saved state file)
7799 */
7800 ComObjPtr <Progress> progress;
7801 progress.createObject();
7802 {
7803 ULONG opCount = 1 + mData->mCurrentSnapshot->data()
7804 .mMachine->mHDData->mHDAttachments.size();
7805 if (mData->mCurrentSnapshot->stateFilePath())
7806 ++ opCount;
7807 progress->init (mParent, aInitiator,
7808 Bstr (tr ("Discarding current machine state")),
7809 FALSE /* aCancelable */, opCount,
7810 Bstr (tr ("Preparing to discard current state")));
7811 }
7812
7813 /* create and start the task on a separate thread */
7814 DiscardCurrentStateTask *task =
7815 new DiscardCurrentStateTask (this, progress, false /* discardCurSnapshot */);
7816 int vrc = RTThreadCreate (NULL, taskHandler,
7817 (void *) task,
7818 0, RTTHREADTYPE_MAIN_WORKER, 0, "DiscardCurState");
7819 if (VBOX_FAILURE (vrc))
7820 delete task;
7821 ComAssertRCRet (vrc, E_FAIL);
7822
7823 /* set the proper machine state (note: after creating a Task instance) */
7824 setMachineState (MachineState_Discarding);
7825
7826 /* return the progress to the caller */
7827 progress.queryInterfaceTo (aProgress);
7828
7829 /* return the new state to the caller */
7830 *aMachineState = mData->mMachineState;
7831
7832 return S_OK;
7833}
7834
7835/**
7836 * @note Locks mParent + other objects for writing!
7837 */
7838STDMETHODIMP SessionMachine::DiscardCurrentSnapshotAndState (
7839 IConsole *aInitiator, MachineState_T *aMachineState, IProgress **aProgress)
7840{
7841 LogFlowThisFunc (("\n"));
7842
7843 AssertReturn (aInitiator, E_INVALIDARG);
7844 AssertReturn (aMachineState && aProgress, E_POINTER);
7845
7846 AutoCaller autoCaller (this);
7847 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7848
7849 /* Progress::init() needs mParent lock */
7850 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
7851
7852 ComAssertRet (mData->mMachineState < MachineState_Running, E_FAIL);
7853
7854 if (mData->mCurrentSnapshot.isNull())
7855 return setError (E_FAIL,
7856 tr ("Could not discard the current state of the machine '%ls' "
7857 "because it doesn't have any snapshots"),
7858 mUserData->mName.raw());
7859
7860 /*
7861 * create a progress object. The number of operations is:
7862 * 1 (preparing) + # of VDIs in the current snapshot +
7863 * # of VDIs in the previous snapshot +
7864 * 1 (if we need to copy the saved state file of the previous snapshot)
7865 * or (if there is no previous snapshot):
7866 * 1 (preparing) + # of VDIs in the current snapshot * 2 +
7867 * 1 (if we need to copy the saved state file of the current snapshot)
7868 */
7869 ComObjPtr <Progress> progress;
7870 progress.createObject();
7871 {
7872 ComObjPtr <Snapshot> curSnapshot = mData->mCurrentSnapshot;
7873 ComObjPtr <Snapshot> prevSnapshot = mData->mCurrentSnapshot->parent();
7874
7875 ULONG opCount = 1;
7876 if (prevSnapshot)
7877 {
7878 opCount += curSnapshot->data().mMachine->mHDData->mHDAttachments.size();
7879 opCount += prevSnapshot->data().mMachine->mHDData->mHDAttachments.size();
7880 if (prevSnapshot->stateFilePath())
7881 ++ opCount;
7882 }
7883 else
7884 {
7885 opCount += curSnapshot->data().mMachine->mHDData->mHDAttachments.size() * 2;
7886 if (curSnapshot->stateFilePath())
7887 ++ opCount;
7888 }
7889
7890 progress->init (mParent, aInitiator,
7891 Bstr (tr ("Discarding current machine snapshot and state")),
7892 FALSE /* aCancelable */, opCount,
7893 Bstr (tr ("Preparing to discard current snapshot and state")));
7894 }
7895
7896 /* create and start the task on a separate thread */
7897 DiscardCurrentStateTask *task =
7898 new DiscardCurrentStateTask (this, progress, true /* discardCurSnapshot */);
7899 int vrc = RTThreadCreate (NULL, taskHandler,
7900 (void *) task,
7901 0, RTTHREADTYPE_MAIN_WORKER, 0, "DiscardCurState");
7902 if (VBOX_FAILURE (vrc))
7903 delete task;
7904 ComAssertRCRet (vrc, E_FAIL);
7905
7906 /* set the proper machine state (note: after creating a Task instance) */
7907 setMachineState (MachineState_Discarding);
7908
7909 /* return the progress to the caller */
7910 progress.queryInterfaceTo (aProgress);
7911
7912 /* return the new state to the caller */
7913 *aMachineState = mData->mMachineState;
7914
7915 return S_OK;
7916}
7917
7918// public methods only for internal purposes
7919/////////////////////////////////////////////////////////////////////////////
7920
7921/**
7922 * Called from the client watcher thread to check for unexpected client
7923 * process death.
7924 *
7925 * @note On Win32, this method is called only when we've got the semaphore
7926 * (i.e. it has been signaled when we were waiting for it).
7927 *
7928 * On Win32, this method always returns true.
7929 *
7930 * On Linux, the method returns true if the client process has terminated
7931 * abnormally (and/or the session has been uninitialized) and false if it is
7932 * still alive.
7933 *
7934 * @note Locks this object for writing.
7935 */
7936bool SessionMachine::checkForDeath()
7937{
7938 Uninit::Reason reason;
7939 bool doUninit = false;
7940 bool rc = false;
7941
7942 /*
7943 * Enclose autoCaller with a block because calling uninit()
7944 * from under it will deadlock.
7945 */
7946 {
7947 AutoCaller autoCaller (this);
7948 if (!autoCaller.isOk())
7949 {
7950 /*
7951 * return true if not ready, to cause the client watcher to exclude
7952 * the corresponding session from watching
7953 */
7954 LogFlowThisFunc (("Already uninitialized!"));
7955 return true;
7956 }
7957
7958 AutoLock alock (this);
7959
7960 /*
7961 * Determine the reason of death: if the session state is Closing here,
7962 * everything is fine. Otherwise it means that the client did not call
7963 * OnSessionEnd() before it released the IPC semaphore.
7964 * This may happen either because the client process has abnormally
7965 * terminated, or because it simply forgot to call ISession::Close()
7966 * before exiting. We threat the latter also as an abnormal termination
7967 * (see Session::uninit() for details).
7968 */
7969 reason = mData->mSession.mState == SessionState_SessionClosing ?
7970 Uninit::Normal :
7971 Uninit::Abnormal;
7972
7973#if defined(__WIN__)
7974
7975 AssertMsg (mIPCSem, ("semaphore must be created"));
7976
7977 if (reason == Uninit::Abnormal)
7978 {
7979 LogWarningThisFunc (("ABNORMAL client termination! (wasRunning=%d)\n",
7980 mData->mMachineState >= MachineState_Running));
7981
7982 /* reset the state to Aborted */
7983 if (mData->mMachineState != MachineState_Aborted)
7984 setMachineState (MachineState_Aborted);
7985 }
7986
7987 /* release the IPC mutex */
7988 ::ReleaseMutex (mIPCSem);
7989
7990 doUninit = true;
7991
7992 rc = true;
7993
7994#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
7995
7996 AssertMsg (mIPCSem >= 0, ("semaphore must be created"));
7997
7998 int val = ::semctl (mIPCSem, 0, GETVAL);
7999 if (val > 0)
8000 {
8001 /* the semaphore is signaled, meaning the session is terminated */
8002
8003 if (reason == Uninit::Abnormal)
8004 {
8005 LogWarningThisFunc (("ABNORMAL client termination! (wasRunning=%d)\n",
8006 mData->mMachineState >= MachineState_Running));
8007
8008 /* reset the state to Aborted */
8009 if (mData->mMachineState != MachineState_Aborted)
8010 setMachineState (MachineState_Aborted);
8011 }
8012
8013 doUninit = true;
8014 }
8015
8016 rc = val > 0;
8017
8018#endif
8019
8020 } /* AutoCaller block */
8021
8022 if (doUninit)
8023 uninit (reason);
8024
8025 return rc;
8026}
8027
8028/**
8029 * @note Locks this object for reading.
8030 */
8031HRESULT SessionMachine::onDVDDriveChange()
8032{
8033 LogFlowThisFunc (("\n"));
8034
8035 AutoCaller autoCaller (this);
8036 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8037
8038 ComPtr <IInternalSessionControl> directControl;
8039 {
8040 AutoReaderLock alock (this);
8041 directControl = mData->mSession.mDirectControl;
8042 }
8043
8044 /* ignore notifications sent after #OnSessionEnd() is called */
8045 if (!directControl)
8046 return S_OK;
8047
8048 return directControl->OnDVDDriveChange();
8049}
8050
8051/**
8052 * @note Locks this object for reading.
8053 */
8054HRESULT SessionMachine::onFloppyDriveChange()
8055{
8056 LogFlowThisFunc (("\n"));
8057
8058 AutoCaller autoCaller (this);
8059 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8060
8061 ComPtr <IInternalSessionControl> directControl;
8062 {
8063 AutoReaderLock alock (this);
8064 directControl = mData->mSession.mDirectControl;
8065 }
8066
8067 /* ignore notifications sent after #OnSessionEnd() is called */
8068 if (!directControl)
8069 return S_OK;
8070
8071 return directControl->OnFloppyDriveChange();
8072}
8073
8074/**
8075 * @note Locks this object for reading.
8076 */
8077HRESULT SessionMachine::onNetworkAdapterChange(INetworkAdapter *networkAdapter)
8078{
8079 LogFlowThisFunc (("\n"));
8080
8081 AutoCaller autoCaller (this);
8082 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8083
8084 ComPtr <IInternalSessionControl> directControl;
8085 {
8086 AutoReaderLock alock (this);
8087 directControl = mData->mSession.mDirectControl;
8088 }
8089
8090 /* ignore notifications sent after #OnSessionEnd() is called */
8091 if (!directControl)
8092 return S_OK;
8093
8094 return directControl->OnNetworkAdapterChange(networkAdapter);
8095}
8096
8097/**
8098 * @note Locks this object for reading.
8099 */
8100HRESULT SessionMachine::onVRDPServerChange()
8101{
8102 LogFlowThisFunc (("\n"));
8103
8104 AutoCaller autoCaller (this);
8105 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8106
8107 ComPtr <IInternalSessionControl> directControl;
8108 {
8109 AutoReaderLock alock (this);
8110 directControl = mData->mSession.mDirectControl;
8111 }
8112
8113 /* ignore notifications sent after #OnSessionEnd() is called */
8114 if (!directControl)
8115 return S_OK;
8116
8117 return directControl->OnVRDPServerChange();
8118}
8119
8120/**
8121 * @note Locks this object for reading.
8122 */
8123HRESULT SessionMachine::onUSBControllerChange()
8124{
8125 LogFlowThisFunc (("\n"));
8126
8127 AutoCaller autoCaller (this);
8128 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8129
8130 ComPtr <IInternalSessionControl> directControl;
8131 {
8132 AutoReaderLock alock (this);
8133 directControl = mData->mSession.mDirectControl;
8134 }
8135
8136 /* ignore notifications sent after #OnSessionEnd() is called */
8137 if (!directControl)
8138 return S_OK;
8139
8140 return directControl->OnUSBControllerChange();
8141}
8142
8143/**
8144 * @note Locks this object for reading.
8145 */
8146HRESULT SessionMachine::onUSBDeviceAttach (IUSBDevice *aDevice)
8147{
8148 LogFlowThisFunc (("\n"));
8149
8150 AutoCaller autoCaller (this);
8151 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8152
8153 ComPtr <IInternalSessionControl> directControl;
8154 {
8155 AutoReaderLock alock (this);
8156 directControl = mData->mSession.mDirectControl;
8157 }
8158
8159 /* ignore notifications sent after #OnSessionEnd() is called */
8160 if (!directControl)
8161 return S_OK;
8162
8163 return directControl->OnUSBDeviceAttach (aDevice);
8164}
8165
8166/**
8167 * @note Locks this object for reading.
8168 */
8169HRESULT SessionMachine::onUSBDeviceDetach (INPTR GUIDPARAM aId)
8170{
8171 LogFlowThisFunc (("\n"));
8172
8173 AutoCaller autoCaller (this);
8174 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8175
8176 ComPtr <IInternalSessionControl> directControl;
8177 {
8178 AutoReaderLock alock (this);
8179 directControl = mData->mSession.mDirectControl;
8180 }
8181
8182 /* ignore notifications sent after #OnSessionEnd() is called */
8183 if (!directControl)
8184 return S_OK;
8185
8186 return directControl->OnUSBDeviceDetach (aId);
8187}
8188
8189// protected methods
8190/////////////////////////////////////////////////////////////////////////////
8191
8192/**
8193 * Helper method to finalize saving the state.
8194 *
8195 * @note Must be called from under this object's lock.
8196 *
8197 * @param aSuccess TRUE if the snapshot has been taken successfully
8198 *
8199 * @note Locks mParent + this objects for writing.
8200 */
8201HRESULT SessionMachine::endSavingState (BOOL aSuccess)
8202{
8203 LogFlowThisFuncEnter();
8204
8205 AutoCaller autoCaller (this);
8206 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8207
8208 /* mParent->removeProgress() needs mParent lock */
8209 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
8210
8211 HRESULT rc = S_OK;
8212
8213 if (aSuccess)
8214 {
8215 mSSData->mStateFilePath = mSnapshotData.mStateFilePath;
8216
8217 /* save all VM settings */
8218 rc = saveSettings();
8219 }
8220 else
8221 {
8222 /* delete the saved state file (it might have been already created) */
8223 RTFileDelete (Utf8Str (mSnapshotData.mStateFilePath));
8224 }
8225
8226 /* remove the completed progress object */
8227 mParent->removeProgress (mSnapshotData.mProgressId);
8228
8229 /* clear out the temporary saved state data */
8230 mSnapshotData.mLastState = MachineState_InvalidMachineState;
8231 mSnapshotData.mProgressId.clear();
8232 mSnapshotData.mStateFilePath.setNull();
8233
8234 LogFlowThisFuncLeave();
8235 return rc;
8236}
8237
8238/**
8239 * Helper method to finalize taking a snapshot.
8240 * Gets called only from #EndTakingSnapshot() that is expected to
8241 * be called by the VM process when it finishes *all* the tasks related to
8242 * taking a snapshot, either scucessfully or unsuccessfilly.
8243 *
8244 * @param aSuccess TRUE if the snapshot has been taken successfully
8245 *
8246 * @note Locks mParent + this objects for writing.
8247 */
8248HRESULT SessionMachine::endTakingSnapshot (BOOL aSuccess)
8249{
8250 LogFlowThisFuncEnter();
8251
8252 AutoCaller autoCaller (this);
8253 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8254
8255 /* Progress object uninitialization needs mParent lock */
8256 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
8257
8258 HRESULT rc = S_OK;
8259
8260 if (aSuccess)
8261 {
8262 /* the server progress must be completed on success */
8263 Assert (mSnapshotData.mServerProgress->completed());
8264
8265 mData->mCurrentSnapshot = mSnapshotData.mSnapshot;
8266 /* memorize the first snapshot if necessary */
8267 if (!mData->mFirstSnapshot)
8268 mData->mFirstSnapshot = mData->mCurrentSnapshot;
8269
8270 int opFlags = SaveSS_AddOp | SaveSS_UpdateCurrentId;
8271 if (mSnapshotData.mLastState != MachineState_Paused && !isModified())
8272 {
8273 /*
8274 * the machine was powered off or saved when taking a snapshot,
8275 * so reset the mCurrentStateModified flag
8276 */
8277 mData->mCurrentStateModified = FALSE;
8278 opFlags |= SaveSS_UpdateCurStateModified;
8279 }
8280
8281 rc = saveSnapshotSettings (mSnapshotData.mSnapshot, opFlags);
8282 }
8283
8284 if (!aSuccess || FAILED (rc))
8285 {
8286 if (mSnapshotData.mSnapshot)
8287 {
8288 /* wait for the completion of the server progress (diff VDI creation) */
8289 /// @todo (dmik) later, we will definitely want to cancel it instead
8290 // (when the cancel function is implemented)
8291 mSnapshotData.mServerProgress->WaitForCompletion (-1);
8292
8293 /*
8294 * delete all differencing VDIs created
8295 * (this will attach their parents back)
8296 */
8297 rc = deleteSnapshotDiffs (mSnapshotData.mSnapshot);
8298 /* continue cleanup on error */
8299
8300 /* delete the saved state file (it might have been already created) */
8301 if (mSnapshotData.mSnapshot->stateFilePath())
8302 RTFileDelete (Utf8Str (mSnapshotData.mSnapshot->stateFilePath()));
8303
8304 mSnapshotData.mSnapshot->uninit();
8305 }
8306 }
8307
8308 /* inform callbacks */
8309 if (aSuccess && SUCCEEDED (rc))
8310 mParent->onSnapshotTaken (mData->mUuid, mSnapshotData.mSnapshot->data().mId);
8311
8312 /* clear out the snapshot data */
8313 mSnapshotData.mLastState = MachineState_InvalidMachineState;
8314 mSnapshotData.mSnapshot.setNull();
8315 mSnapshotData.mServerProgress.setNull();
8316 /* uninitialize the combined progress (to remove it from the VBox collection) */
8317 if (!mSnapshotData.mCombinedProgress.isNull())
8318 {
8319 mSnapshotData.mCombinedProgress->uninit();
8320 mSnapshotData.mCombinedProgress.setNull();
8321 }
8322
8323 LogFlowThisFuncLeave();
8324 return rc;
8325}
8326
8327/**
8328 * Take snapshot task handler.
8329 * Must be called only by TakeSnapshotTask::handler()!
8330 *
8331 * The sole purpose of this task is to asynchronously create differencing VDIs
8332 * and copy the saved state file (when necessary). The VM process will wait
8333 * for this task to complete using the mSnapshotData.mServerProgress
8334 * returned to it.
8335 *
8336 * @note Locks mParent + this objects for writing.
8337 */
8338void SessionMachine::takeSnapshotHandler (TakeSnapshotTask &aTask)
8339{
8340 LogFlowThisFuncEnter();
8341
8342 AutoCaller autoCaller (this);
8343
8344 LogFlowThisFunc (("state=%d\n", autoCaller.state()));
8345 if (!autoCaller.isOk())
8346 {
8347 /*
8348 * we might have been uninitialized because the session was
8349 * accidentally closed by the client, so don't assert
8350 */
8351 LogFlowThisFuncLeave();
8352 return;
8353 }
8354
8355 /* endTakingSnapshot() needs mParent lock */
8356 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
8357
8358 HRESULT rc = S_OK;
8359
8360 LogFlowThisFunc (("Creating differencing VDIs...\n"));
8361
8362 /* create new differencing hard disks and attach them to this machine */
8363 rc = createSnapshotDiffs (&mSnapshotData.mSnapshot->data().mId,
8364 mUserData->mSnapshotFolderFull,
8365 mSnapshotData.mServerProgress,
8366 true /* aOnline */);
8367
8368 if (SUCCEEDED (rc) && mSnapshotData.mLastState == MachineState_Saved)
8369 {
8370 Utf8Str stateFrom = mSSData->mStateFilePath;
8371 Utf8Str stateTo = mSnapshotData.mSnapshot->stateFilePath();
8372
8373 LogFlowThisFunc (("Copying the execution state from '%s' to '%s'...\n",
8374 stateFrom.raw(), stateTo.raw()));
8375
8376 mSnapshotData.mServerProgress->advanceOperation (
8377 Bstr (tr ("Copying the execution state")));
8378
8379 /*
8380 * We can safely leave the lock here:
8381 * mMachineState is MachineState_Saving here
8382 */
8383 alock.leave();
8384
8385 /* copy the state file */
8386 int vrc = RTFileCopyEx (stateFrom, stateTo, progressCallback,
8387 static_cast <Progress *> (mSnapshotData.mServerProgress));
8388
8389 alock.enter();
8390
8391 if (VBOX_FAILURE (vrc))
8392 rc = setError (E_FAIL,
8393 tr ("Could not copy the state file '%ls' to '%ls' (%Vrc)"),
8394 stateFrom.raw(), stateTo.raw());
8395 }
8396
8397 /*
8398 * we have to call endTakingSnapshot() here if the snapshot was taken
8399 * offline, because the VM process will not do it in this case
8400 */
8401 if (mSnapshotData.mLastState != MachineState_Paused)
8402 {
8403 LogFlowThisFunc (("Finalizing the taken snapshot (rc=%08X)...\n", rc));
8404
8405 setMachineState (mSnapshotData.mLastState);
8406 updateMachineStateOnClient();
8407
8408 /* finalize the progress after setting the state, for consistency */
8409 mSnapshotData.mServerProgress->notifyComplete (rc);
8410
8411 endTakingSnapshot (SUCCEEDED (rc));
8412 }
8413 else
8414 {
8415 mSnapshotData.mServerProgress->notifyComplete (rc);
8416 }
8417
8418 LogFlowThisFuncLeave();
8419}
8420
8421/**
8422 * Discard snapshot task handler.
8423 * Must be called only by DiscardSnapshotTask::handler()!
8424 *
8425 * When aTask.subTask is true, the associated progress object is left
8426 * uncompleted on success. On failure, the progress is marked as completed
8427 * regardless of this parameter.
8428 *
8429 * @note Locks mParent + this + child objects for writing!
8430 */
8431void SessionMachine::discardSnapshotHandler (DiscardSnapshotTask &aTask)
8432{
8433 LogFlowThisFuncEnter();
8434
8435 AutoCaller autoCaller (this);
8436
8437 LogFlowThisFunc (("state=%d\n", autoCaller.state()));
8438 if (!autoCaller.isOk())
8439 {
8440 /*
8441 * we might have been uninitialized because the session was
8442 * accidentally closed by the client, so don't assert
8443 */
8444 aTask.progress->notifyComplete (
8445 E_FAIL, COM_IIDOF (IMachine), getComponentName(),
8446 tr ("The session has been accidentally closed"));
8447
8448 LogFlowThisFuncLeave();
8449 return;
8450 }
8451
8452 ComObjPtr <SnapshotMachine> sm = aTask.snapshot->data().mMachine;
8453
8454 /* mParent is locked because of Progress::notifyComplete(), etc. */
8455 AutoMultiLock <3> alock (mParent->wlock(), this->wlock(), sm->rlock());
8456
8457 /* Safe locking in the direction parent->child */
8458 AutoLock snapshotLock (aTask.snapshot);
8459 AutoLock snapshotChildrenLock (aTask.snapshot->childrenLock());
8460
8461 HRESULT rc = S_OK;
8462
8463 /* save the snapshot ID (for callbacks) */
8464 Guid snapshotId = aTask.snapshot->data().mId;
8465
8466 do
8467 {
8468 /* first pass: */
8469 LogFlowThisFunc (("Check hard disk accessibility and affected machines...\n"));
8470
8471 HDData::HDAttachmentList::const_iterator it;
8472 for (it = sm->mHDData->mHDAttachments.begin();
8473 it != sm->mHDData->mHDAttachments.end();
8474 ++ it)
8475 {
8476 ComObjPtr <HardDiskAttachment> hda = *it;
8477 ComObjPtr <HardDisk> hd = hda->hardDisk();
8478 ComObjPtr <HardDisk> parent = hd->parent();
8479
8480 AutoLock hdLock (hd);
8481
8482 if (hd->hasForeignChildren())
8483 {
8484 rc = setError (E_FAIL,
8485 tr ("One or more hard disks belonging to other machines are "
8486 "based on the hard disk '%ls' stored in the snapshot '%ls'"),
8487 hd->toString().raw(), aTask.snapshot->data().mName.raw());
8488 break;
8489 }
8490
8491 if (hd->type() == HardDiskType_NormalHardDisk)
8492 {
8493 AutoLock hdChildrenLock (hd->childrenLock());
8494 size_t childrenCount = hd->children().size();
8495 if (childrenCount > 1)
8496 {
8497 rc = setError (E_FAIL,
8498 tr ("Normal hard disk '%ls' stored in the snapshot '%ls' "
8499 "has more than one child hard disk (%d)"),
8500 hd->toString().raw(), aTask.snapshot->data().mName.raw(),
8501 childrenCount);
8502 break;
8503 }
8504 }
8505 else
8506 {
8507 ComAssertMsgFailedBreak (("Invalid hard disk type %d\n", hd->type()),
8508 rc = E_FAIL);
8509 }
8510
8511 Bstr accessError;
8512 rc = hd->getAccessibleWithChildren (accessError);
8513 CheckComRCBreakRC (rc);
8514
8515 if (!accessError.isNull())
8516 {
8517 rc = setError (E_FAIL,
8518 tr ("Hard disk '%ls' stored in the snapshot '%ls' is not "
8519 "accessible (%ls)"),
8520 hd->toString().raw(), aTask.snapshot->data().mName.raw(),
8521 accessError.raw());
8522 break;
8523 }
8524
8525 rc = hd->setBusyWithChildren();
8526 if (FAILED (rc))
8527 {
8528 /* reset the busy flag of all previous hard disks */
8529 while (it != sm->mHDData->mHDAttachments.begin())
8530 (*(-- it))->hardDisk()->clearBusyWithChildren();
8531 break;
8532 }
8533 }
8534
8535 CheckComRCBreakRC (rc);
8536
8537 /* second pass: */
8538 LogFlowThisFunc (("Performing actual vdi merging...\n"));
8539
8540 for (it = sm->mHDData->mHDAttachments.begin();
8541 it != sm->mHDData->mHDAttachments.end();
8542 ++ it)
8543 {
8544 ComObjPtr <HardDiskAttachment> hda = *it;
8545 ComObjPtr <HardDisk> hd = hda->hardDisk();
8546 ComObjPtr <HardDisk> parent = hd->parent();
8547
8548 AutoLock hdLock (hd);
8549
8550 Bstr hdRootString = hd->root()->toString (true /* aShort */);
8551
8552 if (parent)
8553 {
8554 if (hd->isParentImmutable())
8555 {
8556 aTask.progress->advanceOperation (Bstr (Utf8StrFmt (
8557 tr ("Discarding changes to immutable hard disk '%ls'"),
8558 hdRootString.raw())));
8559
8560 /* clear the busy flag before unregistering */
8561 hd->clearBusy();
8562
8563 /*
8564 * unregisterDiffHardDisk() is supposed to delete and uninit
8565 * the differencing hard disk
8566 */
8567 rc = mParent->unregisterDiffHardDisk (hd);
8568 CheckComRCBreakRC (rc);
8569 continue;
8570 }
8571 else
8572 {
8573 /*
8574 * differencing VDI:
8575 * merge this image to all its children
8576 */
8577
8578 aTask.progress->advanceOperation (Bstr (Utf8StrFmt (
8579 tr ("Merging changes to normal hard disk '%ls' to children"),
8580 hdRootString.raw())));
8581
8582 snapshotChildrenLock.unlock();
8583 snapshotLock.unlock();
8584 alock.leave();
8585
8586 rc = hd->asVDI()->mergeImageToChildren (aTask.progress);
8587
8588 alock.enter();
8589 snapshotLock.lock();
8590 snapshotChildrenLock.lock();
8591
8592 // debug code
8593 // if (it != sm->mHDData->mHDAttachments.begin())
8594 // {
8595 // rc = setError (E_FAIL, "Simulated failure");
8596 // break;
8597 //}
8598
8599 if (SUCCEEDED (rc))
8600 rc = mParent->unregisterDiffHardDisk (hd);
8601 else
8602 hd->clearBusyWithChildren();
8603
8604 CheckComRCBreakRC (rc);
8605 }
8606 }
8607 else if (hd->type() == HardDiskType_NormalHardDisk)
8608 {
8609 /*
8610 * normal vdi has the only child or none
8611 * (checked in the first pass)
8612 */
8613
8614 ComObjPtr <HardDisk> child;
8615 {
8616 AutoLock hdChildrenLock (hd->childrenLock());
8617 if (hd->children().size())
8618 child = hd->children().front();
8619 }
8620
8621 if (child.isNull())
8622 {
8623 aTask.progress->advanceOperation (Bstr (Utf8StrFmt (
8624 tr ("Detaching normal hard disk '%ls'"),
8625 hdRootString.raw())));
8626
8627 /* just deassociate the normal image from this machine */
8628 hd->setMachineId (Guid());
8629 hd->setSnapshotId (Guid());
8630
8631 /* clear the busy flag */
8632 hd->clearBusy();
8633 }
8634 else
8635 {
8636 AutoLock childLock (child);
8637
8638 aTask.progress->advanceOperation (Bstr (Utf8StrFmt (
8639 tr ("Preserving changes to normal hard disk '%ls'"),
8640 hdRootString.raw())));
8641
8642 ComObjPtr <Machine> cm;
8643 ComObjPtr <Snapshot> cs;
8644 ComObjPtr <HardDiskAttachment> childHda;
8645 rc = findHardDiskAttachment (child, &cm, &cs, &childHda);
8646 CheckComRCBreakRC (rc);
8647 /* must be the same machine (checked in the first pass) */
8648 ComAssertBreak (cm->mData->mUuid == mData->mUuid, rc = E_FAIL);
8649
8650 /* merge the child to this basic image */
8651
8652 snapshotChildrenLock.unlock();
8653 snapshotLock.unlock();
8654 alock.leave();
8655
8656 rc = child->asVDI()->mergeImageToParent (aTask.progress);
8657
8658 alock.enter();
8659 snapshotLock.lock();
8660 snapshotChildrenLock.lock();
8661
8662 if (SUCCEEDED (rc))
8663 rc = mParent->unregisterDiffHardDisk (child);
8664 else
8665 hd->clearBusyWithChildren();
8666
8667 CheckComRCBreakRC (rc);
8668
8669 /* replace the child image in the appropriate place */
8670 childHda->updateHardDisk (hd, FALSE /* aDirty */);
8671
8672 if (!cs)
8673 {
8674 aTask.settingsChanged = true;
8675 }
8676 else
8677 {
8678 rc = cm->saveSnapshotSettings (cs, SaveSS_UpdateAllOp);
8679 CheckComRCBreakRC (rc);
8680 }
8681 }
8682 }
8683 else
8684 {
8685 ComAssertMsgFailedBreak (("Invalid hard disk type %d\n", hd->type()),
8686 rc = E_FAIL);
8687 }
8688 }
8689
8690 /* fetch the current error info */
8691 ErrorInfo mergeEi;
8692 HRESULT mergeRc = rc;
8693
8694 if (FAILED (rc))
8695 {
8696 /* clear the busy flag on the rest of hard disks */
8697 for (++ it; it != sm->mHDData->mHDAttachments.end(); ++ it)
8698 (*it)->hardDisk()->clearBusyWithChildren();
8699 }
8700
8701 /*
8702 * we have to try to discard the snapshot even if merging failed
8703 * because some images might have been already merged (and deleted)
8704 */
8705
8706 do
8707 {
8708 LogFlowThisFunc (("Discarding the snapshot (reparenting children)...\n"));
8709
8710 ComObjPtr <Snapshot> parentSnapshot = aTask.snapshot->parent();
8711
8712 /// @todo (dmik):
8713 // when we introduce clones later, discarding the snapshot
8714 // will affect the current and first snapshots of clones, if they are
8715 // direct children of this snapshot. So we will need to lock machines
8716 // associated with child snapshots as well and update mCurrentSnapshot
8717 // and/or mFirstSnapshot fields.
8718
8719 if (aTask.snapshot == mData->mCurrentSnapshot)
8720 {
8721 /* currently, the parent snapshot must refer to the same machine */
8722 ComAssertBreak (
8723 !parentSnapshot ||
8724 parentSnapshot->data().mMachine->mData->mUuid == mData->mUuid,
8725 rc = E_FAIL);
8726 mData->mCurrentSnapshot = parentSnapshot;
8727 /* mark the current state as modified */
8728 mData->mCurrentStateModified = TRUE;
8729 }
8730
8731 if (aTask.snapshot == mData->mFirstSnapshot)
8732 {
8733 /*
8734 * the first snapshot must have only one child when discarded,
8735 * or no children at all
8736 */
8737 ComAssertBreak (aTask.snapshot->children().size() <= 1, rc = E_FAIL);
8738
8739 if (aTask.snapshot->children().size() == 1)
8740 {
8741 ComObjPtr <Snapshot> childSnapshot = aTask.snapshot->children().front();
8742 ComAssertBreak (
8743 childSnapshot->data().mMachine->mData->mUuid == mData->mUuid,
8744 rc = E_FAIL);
8745 mData->mFirstSnapshot = childSnapshot;
8746 }
8747 else
8748 mData->mFirstSnapshot.setNull();
8749 }
8750
8751 /// @todo (dmik)
8752 // if we implement some warning mechanism later, we'll have
8753 // to return a warning if the state file path cannot be deleted
8754 Bstr stateFilePath = aTask.snapshot->stateFilePath();
8755 if (stateFilePath)
8756 RTFileDelete (Utf8Str (stateFilePath));
8757
8758 aTask.snapshot->discard();
8759
8760 rc = saveSnapshotSettings (parentSnapshot,
8761 SaveSS_UpdateAllOp | SaveSS_UpdateCurrentId);
8762 }
8763 while (0);
8764
8765 /* restore the merge error if any */
8766 if (FAILED (mergeRc))
8767 {
8768 rc = mergeRc;
8769 setError (mergeEi);
8770 }
8771 }
8772 while (0);
8773
8774 if (!aTask.subTask || FAILED (rc))
8775 {
8776 if (!aTask.subTask)
8777 {
8778 /* save current error info */
8779 ErrorInfo ei;
8780
8781 /* restore the machine state */
8782 setMachineState (aTask.state);
8783 updateMachineStateOnClient();
8784
8785 /*
8786 * save settings anyway, since we've already changed the current
8787 * machine configuration
8788 */
8789 if (aTask.settingsChanged)
8790 {
8791 saveSettings (true /* aMarkCurStateAsModified */,
8792 true /* aInformCallbacksAnyway */);
8793 }
8794
8795 /* restore current error info */
8796 setError (ei);
8797 }
8798
8799 /* set the result (this will try to fetch current error info on failure) */
8800 aTask.progress->notifyComplete (rc);
8801 }
8802
8803 if (SUCCEEDED (rc))
8804 mParent->onSnapshotDiscarded (mData->mUuid, snapshotId);
8805
8806 LogFlowThisFunc (("Done discarding snapshot (rc=%08X)\n", rc));
8807 LogFlowThisFuncLeave();
8808}
8809
8810/**
8811 * Discard current state task handler.
8812 * Must be called only by DiscardCurrentStateTask::handler()!
8813 *
8814 * @note Locks mParent + this object for writing.
8815 */
8816void SessionMachine::discardCurrentStateHandler (DiscardCurrentStateTask &aTask)
8817{
8818 LogFlowThisFuncEnter();
8819
8820 AutoCaller autoCaller (this);
8821
8822 LogFlowThisFunc (("state=%d\n", autoCaller.state()));
8823 if (!autoCaller.isOk())
8824 {
8825 /*
8826 * we might have been uninitialized because the session was
8827 * accidentally closed by the client, so don't assert
8828 */
8829 aTask.progress->notifyComplete (
8830 E_FAIL, COM_IIDOF (IMachine), getComponentName(),
8831 tr ("The session has been accidentally closed"));
8832
8833 LogFlowThisFuncLeave();
8834 return;
8835 }
8836
8837 /* mParent is locked because of Progress::notifyComplete(), etc. */
8838 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
8839
8840 /*
8841 * discard all current changes to mUserData (name, OSType etc.)
8842 * (note that the machine is powered off, so there is no need
8843 * to inform the direct session)
8844 */
8845 if (isModified())
8846 rollback (false /* aNotify */);
8847
8848 HRESULT rc = S_OK;
8849
8850 bool errorInSubtask = false;
8851 bool stateRestored = false;
8852
8853 const bool isLastSnapshot = mData->mCurrentSnapshot->parent().isNull();
8854
8855 do
8856 {
8857 /*
8858 * discard the saved state file if the machine was Saved prior
8859 * to this operation
8860 */
8861 if (aTask.state == MachineState_Saved)
8862 {
8863 Assert (!mSSData->mStateFilePath.isEmpty());
8864 RTFileDelete (Utf8Str (mSSData->mStateFilePath));
8865 mSSData->mStateFilePath.setNull();
8866 aTask.modifyLastState (MachineState_PoweredOff);
8867 rc = saveStateSettings (SaveSTS_StateFilePath);
8868 CheckComRCBreakRC (rc);
8869 }
8870
8871 if (aTask.discardCurrentSnapshot && !isLastSnapshot)
8872 {
8873 /*
8874 * the "discard current snapshot and state" task is in action,
8875 * the current snapshot is not the last one.
8876 * Discard the current snapshot first.
8877 */
8878
8879 DiscardSnapshotTask subTask (aTask, mData->mCurrentSnapshot);
8880 subTask.subTask = true;
8881 discardSnapshotHandler (subTask);
8882 aTask.settingsChanged = subTask.settingsChanged;
8883 if (aTask.progress->completed())
8884 {
8885 /*
8886 * the progress can be completed by a subtask only if there was
8887 * a failure
8888 */
8889 Assert (FAILED (aTask.progress->resultCode()));
8890 errorInSubtask = true;
8891 rc = aTask.progress->resultCode();
8892 break;
8893 }
8894 }
8895
8896 LONG64 snapshotTimeStamp = 0;
8897
8898 {
8899 ComObjPtr <Snapshot> curSnapshot = mData->mCurrentSnapshot;
8900 AutoLock snapshotLock (curSnapshot);
8901
8902 /* remember the timestamp of the snapshot we're restoring from */
8903 snapshotTimeStamp = curSnapshot->data().mTimeStamp;
8904
8905 /* copy all hardware data from the current snapshot */
8906 copyFrom (curSnapshot->data().mMachine);
8907
8908 LogFlowThisFunc (("Restoring VDIs from the snapshot...\n"));
8909
8910 /* restore the attachmends from the snapshot */
8911 mHDData.backup();
8912 mHDData->mHDAttachments =
8913 curSnapshot->data().mMachine->mHDData->mHDAttachments;
8914
8915 snapshotLock.unlock();
8916 alock.leave();
8917 rc = createSnapshotDiffs (NULL, mUserData->mSnapshotFolderFull,
8918 aTask.progress,
8919 false /* aOnline */);
8920 alock.enter();
8921 snapshotLock.lock();
8922
8923 if (FAILED (rc))
8924 {
8925 /* here we can still safely rollback, so do it */
8926 ErrorInfo ei;
8927 /* undo all changes */
8928 rollback (false /* aNotify */);
8929 setError (ei);
8930 break;
8931 }
8932
8933 /*
8934 * note: old VDIs will be deassociated/deleted on #commit() called
8935 * either from #saveSettings() or directly at the end
8936 */
8937
8938 /* should not have a saved state file associated at this point */
8939 Assert (mSSData->mStateFilePath.isNull());
8940
8941 if (curSnapshot->stateFilePath())
8942 {
8943 Utf8Str snapStateFilePath = curSnapshot->stateFilePath();
8944
8945 Utf8Str stateFilePath = Utf8StrFmt ("%ls%c{%Vuuid}.sav",
8946 mUserData->mSnapshotFolderFull.raw(),
8947 RTPATH_DELIMITER, mData->mUuid.raw());
8948
8949 LogFlowThisFunc (("Copying saved state file from '%s' to '%s'...\n",
8950 snapStateFilePath.raw(), stateFilePath.raw()));
8951
8952 aTask.progress->advanceOperation (
8953 Bstr (tr ("Restoring the execution state")));
8954
8955 /* copy the state file */
8956 snapshotLock.unlock();
8957 alock.leave();
8958 int vrc = RTFileCopyEx (snapStateFilePath, stateFilePath,
8959 progressCallback, aTask.progress);
8960 alock.enter();
8961 snapshotLock.lock();
8962
8963 if (VBOX_SUCCESS (vrc))
8964 {
8965 mSSData->mStateFilePath = stateFilePath;
8966 }
8967 else
8968 {
8969 rc = setError (E_FAIL,
8970 tr ("Could not copy the state file '%s' to '%s' (%Vrc)"),
8971 snapStateFilePath.raw(), stateFilePath.raw(), vrc);
8972 break;
8973 }
8974 }
8975 }
8976
8977 bool informCallbacks = false;
8978
8979 if (aTask.discardCurrentSnapshot && isLastSnapshot)
8980 {
8981 /*
8982 * discard the current snapshot and state task is in action,
8983 * the current snapshot is the last one.
8984 * Discard the current snapshot after discarding the current state.
8985 */
8986
8987 /* commit changes to fixup hard disks before discarding */
8988 rc = commit();
8989 if (SUCCEEDED (rc))
8990 {
8991 DiscardSnapshotTask subTask (aTask, mData->mCurrentSnapshot);
8992 subTask.subTask = true;
8993 discardSnapshotHandler (subTask);
8994 aTask.settingsChanged = subTask.settingsChanged;
8995 if (aTask.progress->completed())
8996 {
8997 /*
8998 * the progress can be completed by a subtask only if there
8999 * was a failure
9000 */
9001 Assert (FAILED (aTask.progress->resultCode()));
9002 errorInSubtask = true;
9003 rc = aTask.progress->resultCode();
9004 }
9005 }
9006
9007 /*
9008 * we've committed already, so inform callbacks anyway to ensure
9009 * they don't miss some change
9010 */
9011 informCallbacks = true;
9012 }
9013
9014 /*
9015 * we have already discarded the current state, so set the
9016 * execution state accordingly no matter of the discard snapshot result
9017 */
9018 if (mSSData->mStateFilePath)
9019 setMachineState (MachineState_Saved);
9020 else
9021 setMachineState (MachineState_PoweredOff);
9022
9023 updateMachineStateOnClient();
9024 stateRestored = true;
9025
9026 if (errorInSubtask)
9027 break;
9028
9029 /* assign the timestamp from the snapshot */
9030 Assert (snapshotTimeStamp != 0);
9031 mData->mLastStateChange = snapshotTimeStamp;
9032
9033 /* mark the current state as not modified */
9034 mData->mCurrentStateModified = FALSE;
9035
9036 /* save all settings and commit */
9037 rc = saveSettings (false /* aMarkCurStateAsModified */,
9038 informCallbacks);
9039 aTask.settingsChanged = false;
9040 }
9041 while (0);
9042
9043 if (FAILED (rc))
9044 {
9045 ErrorInfo ei;
9046
9047 if (!stateRestored)
9048 {
9049 /* restore the machine state */
9050 setMachineState (aTask.state);
9051 updateMachineStateOnClient();
9052 }
9053
9054 /*
9055 * save all settings and commit if still modified (there is no way to
9056 * rollback properly). Note that isModified() will return true after
9057 * copyFrom(). Also save the settings if requested by the subtask.
9058 */
9059 if (isModified() || aTask.settingsChanged)
9060 {
9061 if (aTask.settingsChanged)
9062 saveSettings (true /* aMarkCurStateAsModified */,
9063 true /* aInformCallbacksAnyway */);
9064 else
9065 saveSettings();
9066 }
9067
9068 setError (ei);
9069 }
9070
9071 if (!errorInSubtask)
9072 {
9073 /* set the result (this will try to fetch current error info on failure) */
9074 aTask.progress->notifyComplete (rc);
9075 }
9076
9077 if (SUCCEEDED (rc))
9078 mParent->onSnapshotDiscarded (mData->mUuid, Guid());
9079
9080 LogFlowThisFunc (("Done discarding current state (rc=%08X)\n", rc));
9081
9082 LogFlowThisFuncLeave();
9083}
9084
9085/**
9086 * Helper to change the machine state (reimplementation).
9087 *
9088 * @note Locks this object for writing.
9089 */
9090HRESULT SessionMachine::setMachineState (MachineState_T aMachineState)
9091{
9092 LogFlowThisFuncEnter();
9093 LogFlowThisFunc (("aMachineState=%d\n", aMachineState));
9094
9095 AutoCaller autoCaller (this);
9096 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
9097
9098 AutoLock alock (this);
9099
9100 MachineState_T oldMachineState = mData->mMachineState;
9101
9102 AssertMsgReturn (oldMachineState != aMachineState,
9103 ("oldMachineState=%d, aMachineState=%d\n",
9104 oldMachineState, aMachineState), E_FAIL);
9105
9106 HRESULT rc = S_OK;
9107
9108 int stsFlags = 0;
9109 bool deleteSavedState = false;
9110
9111 /* detect some state transitions */
9112
9113 if (oldMachineState < MachineState_Running &&
9114 aMachineState >= MachineState_Running &&
9115 aMachineState != MachineState_Discarding)
9116 {
9117 /*
9118 * the EMT thread is about to start, so mark attached HDDs as busy
9119 * and all its ancestors as being in use
9120 */
9121 for (HDData::HDAttachmentList::const_iterator it =
9122 mHDData->mHDAttachments.begin();
9123 it != mHDData->mHDAttachments.end();
9124 ++ it)
9125 {
9126 ComObjPtr <HardDisk> hd = (*it)->hardDisk();
9127 AutoLock hdLock (hd);
9128 hd->setBusy();
9129 hd->addReaderOnAncestors();
9130 }
9131 }
9132 else
9133 if (oldMachineState >= MachineState_Running &&
9134 oldMachineState != MachineState_Discarding &&
9135 aMachineState < MachineState_Running)
9136 {
9137 /*
9138 * the EMT thread stopped, so mark attached HDDs as no more busy
9139 * and remove the in-use flag from all its ancestors
9140 */
9141 for (HDData::HDAttachmentList::const_iterator it =
9142 mHDData->mHDAttachments.begin();
9143 it != mHDData->mHDAttachments.end();
9144 ++ it)
9145 {
9146 ComObjPtr <HardDisk> hd = (*it)->hardDisk();
9147 AutoLock hdLock (hd);
9148 hd->releaseReaderOnAncestors();
9149 hd->clearBusy();
9150 }
9151 }
9152
9153 if (oldMachineState == MachineState_Restoring)
9154 {
9155 if (aMachineState != MachineState_Saved)
9156 {
9157 /*
9158 * delete the saved state file once the machine has finished
9159 * restoring from it (note that Console sets the state from
9160 * Restoring to Saved if the VM couldn't restore successfully,
9161 * to give the user an ability to fix an error and retry --
9162 * we keep the saved state file in this case)
9163 */
9164 deleteSavedState = true;
9165 }
9166 }
9167 else
9168 if (oldMachineState == MachineState_Saved &&
9169 (aMachineState == MachineState_PoweredOff ||
9170 aMachineState == MachineState_Aborted))
9171 {
9172 /*
9173 * delete the saved state after Console::DiscardSavedState() is called
9174 * or if the VM process (owning a direct VM session) crashed while the
9175 * VM was Saved
9176 */
9177
9178 /// @todo (dmik)
9179 // Not sure that deleting the saved state file just because of the
9180 // client death before it attempted to restore the VM is a good
9181 // thing. But when it crashes we need to go to the Aborted state
9182 // which cannot have the saved state file associated... The only
9183 // way to fix this is to make the Aborted condition not a VM state
9184 // but a bool flag: i.e., when a crash occurs, set it to true and
9185 // change the state to PoweredOff or Saved depending on the
9186 // saved state presence.
9187
9188 deleteSavedState = true;
9189 mData->mCurrentStateModified = TRUE;
9190 stsFlags |= SaveSTS_CurStateModified;
9191 }
9192
9193 if (aMachineState == MachineState_Starting ||
9194 aMachineState == MachineState_Restoring)
9195 {
9196 /*
9197 * set the current state modified flag to indicate that the
9198 * current state is no more identical to the state in the
9199 * current snapshot
9200 */
9201 if (!mData->mCurrentSnapshot.isNull())
9202 {
9203 mData->mCurrentStateModified = TRUE;
9204 stsFlags |= SaveSTS_CurStateModified;
9205 }
9206 }
9207
9208 if (deleteSavedState == true)
9209 {
9210 Assert (!mSSData->mStateFilePath.isEmpty());
9211 RTFileDelete (Utf8Str (mSSData->mStateFilePath));
9212 mSSData->mStateFilePath.setNull();
9213 stsFlags |= SaveSTS_StateFilePath;
9214 }
9215
9216 /* redirect to the underlying peer machine */
9217 mPeer->setMachineState (aMachineState);
9218
9219 if (aMachineState == MachineState_PoweredOff ||
9220 aMachineState == MachineState_Aborted ||
9221 aMachineState == MachineState_Saved)
9222 {
9223 stsFlags |= SaveSTS_StateTimeStamp;
9224 }
9225
9226 rc = saveStateSettings (stsFlags);
9227
9228 if ((oldMachineState != MachineState_PoweredOff &&
9229 oldMachineState != MachineState_Aborted) &&
9230 (aMachineState == MachineState_PoweredOff ||
9231 aMachineState == MachineState_Aborted))
9232 {
9233 /*
9234 * clear differencing hard disks based on immutable hard disks
9235 * once we've been shut down for any reason
9236 */
9237 rc = wipeOutImmutableDiffs();
9238 }
9239
9240 LogFlowThisFunc (("rc=%08X\n", rc));
9241 LogFlowThisFuncLeave();
9242 return rc;
9243}
9244
9245/**
9246 * Sends the current machine state value to the VM process.
9247 *
9248 * @note Locks this object for reading, then calls a client process.
9249 */
9250HRESULT SessionMachine::updateMachineStateOnClient()
9251{
9252 AutoCaller autoCaller (this);
9253 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
9254
9255 ComPtr <IInternalSessionControl> directControl;
9256 {
9257 AutoReaderLock alock (this);
9258 AssertReturn (!!mData, E_FAIL);
9259 directControl = mData->mSession.mDirectControl;
9260
9261 /* directControl may be already set to NULL here in #OnSessionEnd()
9262 * called too early by the direct session process while there is still
9263 * some operation (like discarding the snapshot) in progress. The client
9264 * process in this case is waiting inside Session::close() for the
9265 * "end session" process object to complete, while #uninit() called by
9266 * #checkForDeath() on the Watcher thread is waiting for the pending
9267 * operation to complete. For now, we accept this inconsitent behavior
9268 * and simply do nothing here. */
9269
9270 if (mData->mSession.mState == SessionState_SessionClosing)
9271 return S_OK;
9272
9273 AssertReturn (!directControl.isNull(), E_FAIL);
9274 }
9275
9276 return directControl->UpdateMachineState (mData->mMachineState);
9277}
9278
9279/* static */
9280DECLCALLBACK(int) SessionMachine::taskHandler (RTTHREAD thread, void *pvUser)
9281{
9282 AssertReturn (pvUser, VERR_INVALID_POINTER);
9283
9284 Task *task = static_cast <Task *> (pvUser);
9285 task->handler();
9286
9287 // it's our responsibility to delete the task
9288 delete task;
9289
9290 return 0;
9291}
9292
9293/////////////////////////////////////////////////////////////////////////////
9294// SnapshotMachine class
9295/////////////////////////////////////////////////////////////////////////////
9296
9297DEFINE_EMPTY_CTOR_DTOR (SnapshotMachine)
9298
9299HRESULT SnapshotMachine::FinalConstruct()
9300{
9301 LogFlowThisFunc (("\n"));
9302
9303 /* set the proper type to indicate we're the SnapshotMachine instance */
9304 unconst (mType) = IsSnapshotMachine;
9305
9306 return S_OK;
9307}
9308
9309void SnapshotMachine::FinalRelease()
9310{
9311 LogFlowThisFunc (("\n"));
9312
9313 uninit();
9314}
9315
9316/**
9317 * Initializes the SnapshotMachine object when taking a snapshot.
9318 *
9319 * @param aSessionMachine machine to take a snapshot from
9320 * @param aSnapshotId snapshot ID of this snapshot machine
9321 * @param aStateFilePath file where the execution state will be later saved
9322 * (or NULL for the offline snapshot)
9323 *
9324 * @note Locks aSessionMachine object for reading.
9325 */
9326HRESULT SnapshotMachine::init (SessionMachine *aSessionMachine,
9327 INPTR GUIDPARAM aSnapshotId,
9328 INPTR BSTR aStateFilePath)
9329{
9330 LogFlowThisFuncEnter();
9331 LogFlowThisFunc (("mName={%ls}\n", aSessionMachine->mUserData->mName.raw()));
9332
9333 AssertReturn (aSessionMachine && !Guid (aSnapshotId).isEmpty(), E_INVALIDARG);
9334
9335 /* Enclose the state transition NotReady->InInit->Ready */
9336 AutoInitSpan autoInitSpan (this);
9337 AssertReturn (autoInitSpan.isOk(), E_UNEXPECTED);
9338
9339 mSnapshotId = aSnapshotId;
9340
9341 AutoReaderLock alock (aSessionMachine);
9342
9343 /* memorize the primary Machine instance (i.e. not SessionMachine!) */
9344 unconst (mPeer) = aSessionMachine->mPeer;
9345 /* share the parent pointer */
9346 unconst (mParent) = mPeer->mParent;
9347
9348 /* take the pointer to Data to share */
9349 mData.share (mPeer->mData);
9350 /*
9351 * take the pointer to UserData to share
9352 * (our UserData must always be the same as Machine's data)
9353 */
9354 mUserData.share (mPeer->mUserData);
9355 /* make a private copy of all other data (recent changes from SessionMachine) */
9356 mHWData.attachCopy (aSessionMachine->mHWData);
9357 mHDData.attachCopy (aSessionMachine->mHDData);
9358
9359 /* SSData is always unique for SnapshotMachine */
9360 mSSData.allocate();
9361 mSSData->mStateFilePath = aStateFilePath;
9362
9363 /*
9364 * create copies of all shared folders (mHWData after attiching a copy
9365 * contains just references to original objects)
9366 */
9367 for (HWData::SharedFolderList::iterator it = mHWData->mSharedFolders.begin();
9368 it != mHWData->mSharedFolders.end();
9369 ++ it)
9370 {
9371 ComObjPtr <SharedFolder> folder;
9372 folder.createObject();
9373 HRESULT rc = folder->initCopy (this, *it);
9374 CheckComRCReturnRC (rc);
9375 *it = folder;
9376 }
9377
9378 /* create all other child objects that will be immutable private copies */
9379
9380 unconst (mBIOSSettings).createObject();
9381 mBIOSSettings->initCopy (this, mPeer->mBIOSSettings);
9382
9383#ifdef VBOX_VRDP
9384 unconst (mVRDPServer).createObject();
9385 mVRDPServer->initCopy (this, mPeer->mVRDPServer);
9386#endif
9387
9388 unconst (mDVDDrive).createObject();
9389 mDVDDrive->initCopy (this, mPeer->mDVDDrive);
9390
9391 unconst (mFloppyDrive).createObject();
9392 mFloppyDrive->initCopy (this, mPeer->mFloppyDrive);
9393
9394 unconst (mAudioAdapter).createObject();
9395 mAudioAdapter->initCopy (this, mPeer->mAudioAdapter);
9396
9397 unconst (mUSBController).createObject();
9398 mUSBController->initCopy (this, mPeer->mUSBController);
9399
9400 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
9401 {
9402 unconst (mNetworkAdapters [slot]).createObject();
9403 mNetworkAdapters [slot]->initCopy (this, mPeer->mNetworkAdapters [slot]);
9404 }
9405
9406 /* Confirm a successful initialization when it's the case */
9407 autoInitSpan.setSucceeded();
9408
9409 LogFlowThisFuncLeave();
9410 return S_OK;
9411}
9412
9413/**
9414 * Initializes the SnapshotMachine object when loading from the settings file.
9415 *
9416 * @param aMachine machine the snapshot belngs to
9417 * @param aHWNode <Hardware> node
9418 * @param aHDAsNode <HardDiskAttachments> node
9419 * @param aSnapshotId snapshot ID of this snapshot machine
9420 * @param aStateFilePath file where the execution state is saved
9421 * (or NULL for the offline snapshot)
9422 *
9423 * @note Locks aMachine object for reading.
9424 */
9425HRESULT SnapshotMachine::init (Machine *aMachine, CFGNODE aHWNode, CFGNODE aHDAsNode,
9426 INPTR GUIDPARAM aSnapshotId, INPTR BSTR aStateFilePath)
9427{
9428 LogFlowThisFuncEnter();
9429 LogFlowThisFunc (("mName={%ls}\n", aMachine->mUserData->mName.raw()));
9430
9431 AssertReturn (aMachine && aHWNode && aHDAsNode && !Guid (aSnapshotId).isEmpty(),
9432 E_INVALIDARG);
9433
9434 /* Enclose the state transition NotReady->InInit->Ready */
9435 AutoInitSpan autoInitSpan (this);
9436 AssertReturn (autoInitSpan.isOk(), E_UNEXPECTED);
9437
9438 mSnapshotId = aSnapshotId;
9439
9440 AutoReaderLock alock (aMachine);
9441
9442 /* memorize the primary Machine instance */
9443 unconst (mPeer) = aMachine;
9444 /* share the parent pointer */
9445 unconst (mParent) = mPeer->mParent;
9446
9447 /* take the pointer to Data to share */
9448 mData.share (mPeer->mData);
9449 /*
9450 * take the pointer to UserData to share
9451 * (our UserData must always be the same as Machine's data)
9452 */
9453 mUserData.share (mPeer->mUserData);
9454 /* allocate private copies of all other data (will be loaded from settings) */
9455 mHWData.allocate();
9456 mHDData.allocate();
9457
9458 /* SSData is always unique for SnapshotMachine */
9459 mSSData.allocate();
9460 mSSData->mStateFilePath = aStateFilePath;
9461
9462 /* create all other child objects that will be immutable private copies */
9463
9464 unconst (mBIOSSettings).createObject();
9465 mBIOSSettings->init (this);
9466
9467#ifdef VBOX_VRDP
9468 unconst (mVRDPServer).createObject();
9469 mVRDPServer->init (this);
9470#endif
9471
9472 unconst (mDVDDrive).createObject();
9473 mDVDDrive->init (this);
9474
9475 unconst (mFloppyDrive).createObject();
9476 mFloppyDrive->init (this);
9477
9478 unconst (mAudioAdapter).createObject();
9479 mAudioAdapter->init (this);
9480
9481 unconst (mUSBController).createObject();
9482 mUSBController->init (this);
9483
9484 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
9485 {
9486 unconst (mNetworkAdapters [slot]).createObject();
9487 mNetworkAdapters [slot]->init (this, slot);
9488 }
9489
9490 /* load hardware and harddisk settings */
9491
9492 HRESULT rc = loadHardware (aHWNode);
9493 if (SUCCEEDED (rc))
9494 rc = loadHardDisks (aHDAsNode, true /* aRegistered */, &mSnapshotId);
9495
9496 if (SUCCEEDED (rc))
9497 {
9498 /* commit all changes made during the initialization */
9499 commit();
9500 }
9501
9502 /* Confirm a successful initialization when it's the case */
9503 if (SUCCEEDED (rc))
9504 autoInitSpan.setSucceeded();
9505
9506 LogFlowThisFuncLeave();
9507 return rc;
9508}
9509
9510/**
9511 * Uninitializes this SnapshotMachine object.
9512 */
9513void SnapshotMachine::uninit()
9514{
9515 LogFlowThisFuncEnter();
9516
9517 /* Enclose the state transition Ready->InUninit->NotReady */
9518 AutoUninitSpan autoUninitSpan (this);
9519 if (autoUninitSpan.uninitDone())
9520 return;
9521
9522 uninitDataAndChildObjects();
9523
9524 unconst (mParent).setNull();
9525 unconst (mPeer).setNull();
9526
9527 LogFlowThisFuncLeave();
9528}
9529
9530// AutoLock::Lockable interface
9531////////////////////////////////////////////////////////////////////////////////
9532
9533/**
9534 * Overrides VirtualBoxBase::lockHandle() in order to share the lock handle
9535 * with the primary Machine instance (mPeer).
9536 */
9537AutoLock::Handle *SnapshotMachine::lockHandle() const
9538{
9539 AssertReturn (!mPeer.isNull(), NULL);
9540 return mPeer->lockHandle();
9541}
9542
9543// public methods only for internal purposes
9544////////////////////////////////////////////////////////////////////////////////
9545
9546/**
9547 * Called by the snapshot object associated with this SnapshotMachine when
9548 * snapshot data such as name or description is changed.
9549 *
9550 * @note Locks this object for writing.
9551 */
9552HRESULT SnapshotMachine::onSnapshotChange (Snapshot *aSnapshot)
9553{
9554 AutoLock alock (this);
9555
9556 mPeer->saveSnapshotSettings (aSnapshot, SaveSS_UpdateAttrsOp);
9557
9558 /* inform callbacks */
9559 mParent->onSnapshotChange (mData->mUuid, aSnapshot->data().mId);
9560
9561 return S_OK;
9562}
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