VirtualBox

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

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

Some adjustments to RTEnv and RTProcCreate. Should work on darwin now.

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

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