VirtualBox

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

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

Continued Main-VMMDev work on memory ballooning.

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

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette