VirtualBox

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

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

Main: Fixed regression caused by r24978: Don't deassociate hard disks when uninitializing SessionMachine objects.

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