VirtualBox

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

Last change on this file since 6056 was 6056, checked in by vboxsync, 17 years ago

first version of a PulseAudio driver backend for Linux (still no pcm_in)

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