VirtualBox

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

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

Main: Rotate VBox.png[.N] files (that may have been created by the GUI) as well.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 332.2 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 = NIL_RTENV;
2812
2813 if (aEnvironment)
2814 {
2815 char *newEnvStr = NULL;
2816
2817 do
2818 {
2819 /* clone the current environment */
2820 int vrc = RTEnvClone(&env, NULL);
2821 AssertRCBreak (vrc, vrc = vrc);
2822
2823 vrc = RTStrUtf8ToCurrentCP (&newEnvStr, Utf8Str (aEnvironment));
2824 AssertRCBreak (vrc, vrc = vrc);
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 if (VBOX_FAILURE (vrc = RTEnvPutEx(env, var)))
2837 break;
2838 var = p + 1;
2839 }
2840 }
2841 if (VBOX_SUCCESS (vrc) && *var)
2842 vrc = RTEnvPutEx(env, var);
2843
2844 AssertRCBreak (vrc, vrc = vrc);
2845 }
2846 while (0);
2847
2848 if (newEnvStr != NULL)
2849 RTStrFree(newEnvStr);
2850 }
2851
2852 Bstr type (aType);
2853 if (type == "gui")
2854 {
2855#ifdef RT_OS_DARWIN /* Avoid Lanuch Services confusing this with the selector by using a helper app. */
2856 const char VirtualBox_exe[] = "../Resources/VirtualBoxVM.app/Contents/MacOS/VirtualBoxVM";
2857#else
2858 const char VirtualBox_exe[] = "VirtualBox" HOSTSUFF_EXE;
2859#endif
2860 Assert (sz >= sizeof (VirtualBox_exe));
2861 strcpy (cmd, VirtualBox_exe);
2862
2863 Utf8Str idStr = mData->mUuid.toString();
2864#ifdef RT_OS_WINDOWS /** @todo drop this once the RTProcCreate bug has been fixed */
2865 const char * args[] = {path, "-startvm", idStr, 0 };
2866#else
2867 Utf8Str name = mUserData->mName;
2868 const char * args[] = {path, "-comment", name, "-startvm", idStr, 0 };
2869#endif
2870 vrc = RTProcCreate (path, args, RTEnvGetArray (env), 0, &pid);
2871 }
2872 else
2873#ifdef VBOX_VRDP
2874 if (type == "vrdp")
2875 {
2876 const char VBoxVRDP_exe[] = "VBoxVRDP" HOSTSUFF_EXE;
2877 Assert (sz >= sizeof (VBoxVRDP_exe));
2878 strcpy (cmd, VBoxVRDP_exe);
2879
2880 Utf8Str idStr = mData->mUuid.toString();
2881#ifdef RT_OS_WINDOWS
2882 const char * args[] = {path, "-startvm", idStr, 0 };
2883#else
2884 Utf8Str name = mUserData->mName;
2885 const char * args[] = {path, "-comment", name, "-startvm", idStr, 0 };
2886#endif
2887 vrc = RTProcCreate (path, args, RTEnvGetArray (env), 0, &pid);
2888 }
2889 else
2890#endif /* VBOX_VRDP */
2891 if (type == "capture")
2892 {
2893 const char VBoxVRDP_exe[] = "VBoxVRDP" HOSTSUFF_EXE;
2894 Assert (sz >= sizeof (VBoxVRDP_exe));
2895 strcpy (cmd, VBoxVRDP_exe);
2896
2897 Utf8Str idStr = mData->mUuid.toString();
2898#ifdef RT_OS_WINDOWS
2899 const char * args[] = {path, "-startvm", idStr, "-capture", 0 };
2900#else
2901 Utf8Str name = mUserData->mName;
2902 const char * args[] = {path, "-comment", name, "-startvm", idStr, "-capture", 0 };
2903#endif
2904 vrc = RTProcCreate (path, args, RTEnvGetArray (env), 0, &pid);
2905 }
2906 else
2907 {
2908 if (env != NIL_RTENV)
2909 RTEnvDestroy (env);
2910 return setError (E_INVALIDARG,
2911 tr ("Invalid session type: '%ls'"), aType);
2912 }
2913
2914 if (env != NIL_RTENV)
2915 RTEnvDestroy (env);
2916
2917 if (VBOX_FAILURE (vrc))
2918 return setError (E_FAIL,
2919 tr ("Could not launch a process for the machine '%ls' (%Vrc)"),
2920 mUserData->mName.raw(), vrc);
2921
2922 LogFlowThisFunc (("launched.pid=%d(0x%x)\n", pid, pid));
2923
2924 /*
2925 * Note that we don't leave the lock here before calling the client,
2926 * because it doesn't need to call us back if called with a NULL argument.
2927 * Leaving the lock herer is dangerous because we didn't prepare the
2928 * launch data yet, but the client we've just started may happen to be
2929 * too fast and call openSession() that will fail (because of PID, etc.),
2930 * so that the Machine will never get out of the Spawning session state.
2931 */
2932
2933 /* inform the session that it will be a remote one */
2934 LogFlowThisFunc (("Calling AssignMachine (NULL)...\n"));
2935 HRESULT rc = aControl->AssignMachine (NULL);
2936 LogFlowThisFunc (("AssignMachine (NULL) returned %08X\n", rc));
2937
2938 if (FAILED (rc))
2939 {
2940 /* restore the session state */
2941 mData->mSession.mState = SessionState_SessionClosed;
2942 /* The failure may w/o any error info (from RPC), so provide one */
2943 return setError (rc,
2944 tr ("Failed to assign the machine to the session"));
2945 }
2946
2947 /* attach launch data to the machine */
2948 Assert (mData->mSession.mPid == NIL_RTPROCESS);
2949 mData->mSession.mRemoteControls.push_back (aControl);
2950 mData->mSession.mProgress = aProgress;
2951 mData->mSession.mPid = pid;
2952 mData->mSession.mState = SessionState_SessionSpawning;
2953 mData->mSession.mType = type;
2954
2955 LogFlowThisFuncLeave();
2956 return S_OK;
2957}
2958
2959/**
2960 * @note Locks this object for writing, calls the client process
2961 * (outside the lock).
2962 */
2963HRESULT Machine::openExistingSession (IInternalSessionControl *aControl)
2964{
2965 LogFlowThisFuncEnter();
2966
2967 AssertReturn (aControl, E_FAIL);
2968
2969 AutoCaller autoCaller (this);
2970 CheckComRCReturnRC (autoCaller.rc());
2971
2972 AutoLock alock (this);
2973
2974 if (!mData->mRegistered)
2975 return setError (E_UNEXPECTED,
2976 tr ("The machine '%ls' is not registered"), mUserData->mName.raw());
2977
2978 LogFlowThisFunc (("mSession.state=%d\n", mData->mSession.mState));
2979
2980 if (mData->mSession.mState != SessionState_SessionOpen)
2981 return setError (E_ACCESSDENIED,
2982 tr ("The machine '%ls' does not have an open session"),
2983 mUserData->mName.raw());
2984
2985 ComAssertRet (!mData->mSession.mDirectControl.isNull(), E_FAIL);
2986
2987 /*
2988 * Get the console from the direct session (note that we don't leave the
2989 * lock here because GetRemoteConsole must not call us back).
2990 */
2991 ComPtr <IConsole> console;
2992 HRESULT rc = mData->mSession.mDirectControl->
2993 GetRemoteConsole (console.asOutParam());
2994 if (FAILED (rc))
2995 {
2996 /* The failure may w/o any error info (from RPC), so provide one */
2997 return setError (rc,
2998 tr ("Failed to get a console object from the direct session"));
2999 }
3000
3001 ComAssertRet (!console.isNull(), E_FAIL);
3002
3003 ComObjPtr <SessionMachine> sessionMachine = mData->mSession.mMachine;
3004 AssertReturn (!sessionMachine.isNull(), E_FAIL);
3005
3006 /*
3007 * Leave the lock before calling the client process. It's safe here
3008 * since the only thing to do after we get the lock again is to add
3009 * the remote control to the list (which doesn't directly influence
3010 * anything).
3011 */
3012 alock.leave();
3013
3014 /* attach the remote session to the machine */
3015 LogFlowThisFunc (("Calling AssignRemoteMachine()...\n"));
3016 rc = aControl->AssignRemoteMachine (sessionMachine, console);
3017 LogFlowThisFunc (("AssignRemoteMachine() returned %08X\n", rc));
3018
3019 /* The failure may w/o any error info (from RPC), so provide one */
3020 if (FAILED (rc))
3021 return setError (rc,
3022 tr ("Failed to assign the machine to the session"));
3023
3024 alock.enter();
3025
3026 /* need to revalidate the state after entering the lock again */
3027 if (mData->mSession.mState != SessionState_SessionOpen)
3028 {
3029 aControl->Uninitialize();
3030
3031 return setError (E_ACCESSDENIED,
3032 tr ("The machine '%ls' does not have an open session"),
3033 mUserData->mName.raw());
3034 }
3035
3036 /* store the control in the list */
3037 mData->mSession.mRemoteControls.push_back (aControl);
3038
3039 LogFlowThisFuncLeave();
3040 return S_OK;
3041}
3042
3043/**
3044 * Checks that the registered flag of the machine can be set according to
3045 * the argument and sets it. On success, commits and saves all settings.
3046 *
3047 * @note When this machine is inaccessible, the only valid value for \a
3048 * aRegistered is FALSE (i.e. unregister the machine) because unregistered
3049 * inaccessible machines are not currently supported. Note that unregistering
3050 * an inaccessible machine will \b uninitialize this machine object. Therefore,
3051 * the caller must make sure there are no active Machine::addCaller() calls
3052 * on the current thread because this will block Machine::uninit().
3053 *
3054 * @note Locks this object and children for writing!
3055 */
3056HRESULT Machine::trySetRegistered (BOOL aRegistered)
3057{
3058 AutoLimitedCaller autoCaller (this);
3059 AssertComRCReturnRC (autoCaller.rc());
3060
3061 AutoLock alock (this);
3062
3063 /* wait for state dependants to drop to zero */
3064 checkStateDependencies (alock);
3065
3066 ComAssertRet (mData->mRegistered != aRegistered, E_FAIL);
3067
3068 if (!mData->mAccessible)
3069 {
3070 /* A special case: the machine is not accessible. */
3071
3072 /* inaccessible machines can only be unregistered */
3073 AssertReturn (!aRegistered, E_FAIL);
3074
3075 /* Uninitialize ourselves here because currently there may be no
3076 * unregistered that are inaccessible (this state combination is not
3077 * supported). Note releasing the caller and leaving the lock before
3078 * calling uninit() */
3079
3080 alock.leave();
3081 autoCaller.release();
3082
3083 uninit();
3084
3085 return S_OK;
3086 }
3087
3088 AssertReturn (autoCaller.state() == Ready, E_FAIL);
3089
3090 if (aRegistered)
3091 {
3092 if (mData->mRegistered)
3093 return setError (E_FAIL,
3094 tr ("The machine '%ls' with UUID {%s} is already registered"),
3095 mUserData->mName.raw(),
3096 mData->mUuid.toString().raw());
3097 }
3098 else
3099 {
3100 if (mData->mMachineState == MachineState_Saved)
3101 return setError (E_FAIL,
3102 tr ("Cannot unregister the machine '%ls' because it "
3103 "is in the Saved state"),
3104 mUserData->mName.raw());
3105
3106 size_t snapshotCount = 0;
3107 if (mData->mFirstSnapshot)
3108 snapshotCount = mData->mFirstSnapshot->descendantCount() + 1;
3109 if (snapshotCount)
3110 return setError (E_FAIL,
3111 tr ("Cannot unregister the machine '%ls' because it "
3112 "has %d snapshots"),
3113 mUserData->mName.raw(), snapshotCount);
3114
3115 if (mData->mSession.mState != SessionState_SessionClosed)
3116 return setError (E_FAIL,
3117 tr ("Cannot unregister the machine '%ls' because it has an "
3118 "open session"),
3119 mUserData->mName.raw());
3120
3121 if (mHDData->mHDAttachments.size() != 0)
3122 return setError (E_FAIL,
3123 tr ("Cannot unregister the machine '%ls' because it "
3124 "has %d hard disks attached"),
3125 mUserData->mName.raw(), mHDData->mHDAttachments.size());
3126 }
3127
3128 /* Ensure the settings are saved. If we are going to be registered and
3129 * isConfigLocked() is FALSE then it means that no config file exists yet,
3130 * so create it. */
3131 if (isModified() || (aRegistered && !isConfigLocked()))
3132 {
3133 HRESULT rc = saveSettings();
3134 CheckComRCReturnRC (rc);
3135 }
3136
3137 mData->mRegistered = aRegistered;
3138
3139 /* inform the USB proxy about all attached/detached USB filters */
3140 mUSBController->onMachineRegistered (aRegistered);
3141
3142 return S_OK;
3143}
3144
3145/**
3146 * Increases the number of objects dependent on the machine state or on the
3147 * registered state. Guarantees that these two states will not change at
3148 * least until #releaseStateDependency() is called.
3149 *
3150 * Depending on the @a aDepType value, additional state checks may be
3151 * made. These checks will set extended error info on failure. See
3152 * #checkStateDependency() for more info.
3153 *
3154 * If this method returns a failure, the dependency is not added and the
3155 * caller is not allowed to rely on any particular machine state or
3156 * registration state value and may return the failed result code to the
3157 * upper level.
3158 *
3159 * @param aDepType Dependency type to add.
3160 * @param aState Current machine state (NULL if not interested).
3161 * @param aRegistered Current registered state (NULL if not interested).
3162 */
3163HRESULT Machine::addStateDependency (StateDependency aDepType /* = AnyStateDep */,
3164 MachineState_T *aState /* = NULL */,
3165 BOOL *aRegistered /* = NULL */)
3166{
3167 AutoCaller autoCaller (this);
3168 AssertComRCReturnRC (autoCaller.rc());
3169
3170 AutoLock alock (this);
3171
3172 if (mData->mWaitingStateDeps && mData->mMachineStateDeps == 0)
3173 {
3174 /* checkStateDependencies() is at the point after RTSemEventWait() but
3175 * before entering the lock. Report an error. It would be better to
3176 * leave the lock now and re-schedule ourselves, but we don't have a
3177 * framework that can guarantee such a behavior in 100% cases. */
3178
3179 AssertFailed(); /* <-- this is just to see how often it can happen */
3180
3181 return setError (E_ACCESSDENIED,
3182 tr ("The machine is busy: state transition is in progress. "
3183 "Retry the operation (state is %d)"),
3184 mData->mMachineState);
3185 }
3186
3187 HRESULT rc = checkStateDependency (aDepType);
3188 CheckComRCReturnRC (rc);
3189
3190 if (aState)
3191 *aState = mData->mMachineState;
3192 if (aRegistered)
3193 *aRegistered = mData->mRegistered;
3194
3195 ++ mData->mMachineStateDeps;
3196
3197 return S_OK;
3198}
3199
3200/**
3201 * Decreases the number of objects dependent on the machine state.
3202 * Must always complete the #addStateDependency() call after the state
3203 * dependency no more necessary.
3204 */
3205void Machine::releaseStateDependency()
3206{
3207 AutoCaller autoCaller (this);
3208 AssertComRCReturnVoid (autoCaller.rc());
3209
3210 AutoLock alock (this);
3211
3212 AssertReturnVoid (mData->mMachineStateDeps > 0);
3213 -- mData->mMachineStateDeps;
3214
3215 if (mData->mMachineStateDeps == 0 &&
3216 mData->mZeroMachineStateDepsSem != NIL_RTSEMEVENT)
3217 {
3218 /* inform checkStateDependencies() that there are no more deps */
3219 RTSemEventSignal (mData->mZeroMachineStateDepsSem);
3220 }
3221}
3222
3223// protected methods
3224/////////////////////////////////////////////////////////////////////////////
3225
3226/**
3227 * Performs machine state checks based on the @a aDepType value. If a check
3228 * fails, this method will set extended error info, otherwise it will return
3229 * S_OK. It is supposed, that on failure, the caller will immedieately return
3230 * the return value of this method to the upper level.
3231 *
3232 * When @a aDepType is AnyStateDep, this method always returns S_OK.
3233 *
3234 * When @a aDepType is MutableStateDep, this method returns S_OK only if the
3235 * current state of this machine object allows to change settings of the
3236 * machine (i.e. the machine is not registered, or registered but not running
3237 * and not saved). It is useful to call this method from Machine setters
3238 * before performing any change.
3239 *
3240 * When @a aDepType is MutableOrSavedStateDep, this method behaves the same
3241 * as for MutableStateDep except that if the machine is saved, S_OK is also
3242 * returned. This is useful in setters which allow changing machine
3243 * properties when it is in the saved state.
3244 *
3245 * @param aDepType Dependency type to check.
3246 *
3247 * @note External classes should use #addStateDependency() and
3248 * #releaseStateDependency() methods or the smart AutoStateDependency
3249 * template.
3250 *
3251 * @note This method must be called from under this object's lock.
3252 */
3253HRESULT Machine::checkStateDependency (StateDependency aDepType)
3254{
3255 AssertReturn (isLockedOnCurrentThread(), E_FAIL);
3256
3257 switch (aDepType)
3258 {
3259 case AnyStateDep:
3260 {
3261 break;
3262 }
3263 case MutableStateDep:
3264 {
3265 if (mData->mRegistered &&
3266 (mType != IsSessionMachine ||
3267 mData->mMachineState > MachineState_Paused ||
3268 mData->mMachineState == MachineState_Saved))
3269 return setError (E_ACCESSDENIED,
3270 tr ("The machine is not mutable (state is %d)"),
3271 mData->mMachineState);
3272 break;
3273 }
3274 case MutableOrSavedStateDep:
3275 {
3276 if (mData->mRegistered &&
3277 (mType != IsSessionMachine ||
3278 mData->mMachineState > MachineState_Paused))
3279 return setError (E_ACCESSDENIED,
3280 tr ("The machine is not mutable (state is %d)"),
3281 mData->mMachineState);
3282 break;
3283 }
3284 }
3285
3286 return S_OK;
3287}
3288
3289/**
3290 * Helper to uninitialize all associated child objects
3291 * and to free all data structures.
3292 *
3293 * This method must be called as a part of the object's uninitialization
3294 * procedure (usually done in the uninit() method).
3295 *
3296 * @note Must be called only from uninit().
3297 */
3298void Machine::uninitDataAndChildObjects()
3299{
3300 AutoCaller autoCaller (this);
3301 AssertComRCReturn (autoCaller.rc(), (void) 0);
3302 AssertComRCReturn (autoCaller.state( ) == InUninit, (void) 0);
3303
3304 /* tell all our child objects we've been uninitialized */
3305
3306 /*
3307 * uninit all children using addDependentChild()/removeDependentChild()
3308 * in their init()/uninit() methods
3309 */
3310 uninitDependentChildren();
3311
3312 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
3313 {
3314 if (mNetworkAdapters [slot])
3315 {
3316 mNetworkAdapters [slot]->uninit();
3317 unconst (mNetworkAdapters [slot]).setNull();
3318 }
3319 }
3320
3321 if (mUSBController)
3322 {
3323 mUSBController->uninit();
3324 unconst (mUSBController).setNull();
3325 }
3326
3327 if (mAudioAdapter)
3328 {
3329 mAudioAdapter->uninit();
3330 unconst (mAudioAdapter).setNull();
3331 }
3332
3333 for (ULONG slot = 0; slot < ELEMENTS (mSerialPorts); slot ++)
3334 {
3335 if (mSerialPorts [slot])
3336 {
3337 mSerialPorts [slot]->uninit();
3338 unconst (mSerialPorts [slot]).setNull();
3339 }
3340 }
3341
3342 for (ULONG slot = 0; slot < ELEMENTS (mParallelPorts); slot ++)
3343 {
3344 if (mParallelPorts [slot])
3345 {
3346 mParallelPorts [slot]->uninit();
3347 unconst (mParallelPorts [slot]).setNull();
3348 }
3349 }
3350
3351 if (mFloppyDrive)
3352 {
3353 mFloppyDrive->uninit();
3354 unconst (mFloppyDrive).setNull();
3355 }
3356
3357 if (mDVDDrive)
3358 {
3359 mDVDDrive->uninit();
3360 unconst (mDVDDrive).setNull();
3361 }
3362
3363#ifdef VBOX_VRDP
3364 if (mVRDPServer)
3365 {
3366 mVRDPServer->uninit();
3367 unconst (mVRDPServer).setNull();
3368 }
3369#endif
3370
3371 if (mBIOSSettings)
3372 {
3373 mBIOSSettings->uninit();
3374 unconst (mBIOSSettings).setNull();
3375 }
3376
3377 /* free data structures */
3378 mSSData.free();
3379 mHDData.free();
3380 mHWData.free();
3381 mUserData.free();
3382 mData.free();
3383}
3384
3385
3386/**
3387 * Chhecks that there are no state dependants. If necessary, waits for the
3388 * number of dependants to drop to zero. Must be called from under
3389 * this object's lock.
3390 *
3391 * @param aLock This object's lock.
3392 *
3393 * @note This method may leave the object lock during its execution!
3394 */
3395void Machine::checkStateDependencies (AutoLock &aLock)
3396{
3397 AssertReturnVoid (isLockedOnCurrentThread());
3398 AssertReturnVoid (aLock.belongsTo (this));
3399
3400 /* Wait for all state dependants if necessary */
3401 if (mData->mMachineStateDeps > 0)
3402 {
3403 /* lazy creation */
3404 if (mData->mZeroMachineStateDepsSem == NIL_RTSEMEVENT)
3405 RTSemEventCreate (&mData->mZeroMachineStateDepsSem);
3406
3407 LogFlowThisFunc (("Waiting for state deps (%d) to drop to zero...\n",
3408 mData->mMachineStateDeps));
3409
3410 mData->mWaitingStateDeps = TRUE;
3411
3412 aLock.leave();
3413
3414 RTSemEventWait (mData->mZeroMachineStateDepsSem, RT_INDEFINITE_WAIT);
3415
3416 aLock.enter();
3417
3418 mData->mWaitingStateDeps = FALSE;
3419 }
3420}
3421
3422/**
3423 * Helper to change the machine state.
3424 *
3425 * @note Locks this object for writing.
3426 */
3427HRESULT Machine::setMachineState (MachineState_T aMachineState)
3428{
3429 LogFlowThisFuncEnter();
3430 LogFlowThisFunc (("aMachineState=%d\n", aMachineState));
3431
3432 AutoCaller autoCaller (this);
3433 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
3434
3435 AutoLock alock (this);
3436
3437 /* wait for state dependants to drop to zero */
3438 /// @todo it may be potentially unsafe to leave the lock here as
3439 // the below method does. Needs some thinking. The easiest solution may
3440 // be to provide a separate mutex for mMachineState and mRegistered.
3441 checkStateDependencies (alock);
3442
3443 if (mData->mMachineState != aMachineState)
3444 {
3445 mData->mMachineState = aMachineState;
3446
3447 RTTIMESPEC time;
3448 mData->mLastStateChange = RTTimeSpecGetMilli(RTTimeNow(&time));
3449
3450 mParent->onMachineStateChange (mData->mUuid, aMachineState);
3451 }
3452
3453 LogFlowThisFuncLeave();
3454 return S_OK;
3455}
3456
3457/**
3458 * Searches for a shared folder with the given logical name
3459 * in the collection of shared folders.
3460 *
3461 * @param aName logical name of the shared folder
3462 * @param aSharedFolder where to return the found object
3463 * @param aSetError whether to set the error info if the folder is
3464 * not found
3465 * @return
3466 * S_OK when found or E_INVALIDARG when not found
3467 *
3468 * @note
3469 * must be called from under the object's lock!
3470 */
3471HRESULT Machine::findSharedFolder (const BSTR aName,
3472 ComObjPtr <SharedFolder> &aSharedFolder,
3473 bool aSetError /* = false */)
3474{
3475 bool found = false;
3476 for (HWData::SharedFolderList::const_iterator it = mHWData->mSharedFolders.begin();
3477 !found && it != mHWData->mSharedFolders.end();
3478 ++ it)
3479 {
3480 AutoLock alock (*it);
3481 found = (*it)->name() == aName;
3482 if (found)
3483 aSharedFolder = *it;
3484 }
3485
3486 HRESULT rc = found ? S_OK : E_INVALIDARG;
3487
3488 if (aSetError && !found)
3489 setError (rc, tr ("Could not find a shared folder named '%ls'"), aName);
3490
3491 return rc;
3492}
3493
3494/**
3495 * Loads all the VM settings by walking down the <Machine> node.
3496 *
3497 * @param aRegistered true when the machine is being loaded on VirtualBox
3498 * startup
3499 *
3500 * @note This method is intended to be called only from init(), so it assumes
3501 * all machine data fields have appropriate default values when it is called.
3502 *
3503 * @note Doesn't lock any objects.
3504 */
3505HRESULT Machine::loadSettings (bool aRegistered)
3506{
3507 LogFlowThisFuncEnter();
3508 AssertReturn (mType == IsMachine, E_FAIL);
3509
3510 AutoCaller autoCaller (this);
3511 AssertReturn (autoCaller.state() == InInit, E_FAIL);
3512
3513 HRESULT rc = S_OK;
3514
3515 CFGHANDLE configLoader = NULL;
3516 char *loaderError = NULL;
3517 int vrc = CFGLDRLoad (&configLoader,
3518 Utf8Str (mData->mConfigFileFull), mData->mHandleCfgFile,
3519 XmlSchemaNS, true, cfgLdrEntityResolver,
3520 &loaderError);
3521 if (VBOX_FAILURE (vrc))
3522 {
3523 rc = setError (E_FAIL,
3524 tr ("Could not load the settings file '%ls' (%Vrc)%s%s"),
3525 mData->mConfigFileFull.raw(), vrc,
3526 loaderError ? ".\n" : "", loaderError ? loaderError : "");
3527
3528 if (loaderError)
3529 RTMemTmpFree (loaderError);
3530
3531 LogFlowThisFuncLeave();
3532 return rc;
3533 }
3534
3535 /*
3536 * When reading the XML, we assume it has been validated, so we don't
3537 * do any structural checks here, Just Assert() some things.
3538 */
3539
3540 CFGNODE machineNode = 0;
3541 CFGLDRGetNode (configLoader, "VirtualBox/Machine", 0, &machineNode);
3542
3543 do
3544 {
3545 ComAssertBreak (machineNode, rc = E_FAIL);
3546
3547 /* uuid (required) */
3548 Guid id;
3549 CFGLDRQueryUUID (machineNode, "uuid", id.ptr());
3550
3551 /* If the stored UUID is not empty, it means the registered machine
3552 * is being loaded. Compare the loaded UUID with the stored one taken
3553 * from the global registry. */
3554 if (!mData->mUuid.isEmpty())
3555 {
3556 if (mData->mUuid != id)
3557 {
3558 rc = setError (E_FAIL,
3559 tr ("Machine UUID {%Vuuid} in '%ls' doesn't match its "
3560 "UUID {%s} in the registry file '%ls'"),
3561 id.raw(), mData->mConfigFileFull.raw(),
3562 mData->mUuid.toString().raw(),
3563 mParent->settingsFileName().raw());
3564 break;
3565 }
3566 }
3567 else
3568 unconst (mData->mUuid) = id;
3569
3570 /* name (required) */
3571 CFGLDRQueryBSTR (machineNode, "name", mUserData->mName.asOutParam());
3572
3573 /* nameSync (optional, default is true) */
3574 {
3575 bool nameSync = true;
3576 CFGLDRQueryBool (machineNode, "nameSync", &nameSync);
3577 mUserData->mNameSync = nameSync;
3578 }
3579
3580 /* Description (optional, default is null) */
3581 {
3582 CFGNODE descNode = 0;
3583 CFGLDRGetChildNode (machineNode, "Description", 0, &descNode);
3584 if (descNode)
3585 {
3586 CFGLDRQueryBSTR (descNode, NULL,
3587 mUserData->mDescription.asOutParam());
3588 CFGLDRReleaseNode (descNode);
3589 }
3590 else
3591 mUserData->mDescription.setNull();
3592 }
3593
3594 /* OSType (required) */
3595 {
3596 CFGLDRQueryBSTR (machineNode, "OSType",
3597 mUserData->mOSTypeId.asOutParam());
3598
3599 /* look up the object by Id to check it is valid */
3600 ComPtr <IGuestOSType> guestOSType;
3601 rc = mParent->GetGuestOSType (mUserData->mOSTypeId,
3602 guestOSType.asOutParam());
3603 if (FAILED (rc))
3604 break;
3605 }
3606
3607 /* stateFile (optional) */
3608 {
3609 Bstr stateFilePath;
3610 CFGLDRQueryBSTR (machineNode, "stateFile", stateFilePath.asOutParam());
3611 if (stateFilePath)
3612 {
3613 Utf8Str stateFilePathFull = stateFilePath;
3614 int vrc = calculateFullPath (stateFilePathFull, stateFilePathFull);
3615 if (VBOX_FAILURE (vrc))
3616 {
3617 rc = setError (E_FAIL,
3618 tr ("Invalid saved state file path: '%ls' (%Vrc)"),
3619 stateFilePath.raw(), vrc);
3620 break;
3621 }
3622 mSSData->mStateFilePath = stateFilePathFull;
3623 }
3624 else
3625 mSSData->mStateFilePath.setNull();
3626 }
3627
3628 /*
3629 * currentSnapshot ID (optional)
3630 * Note that due to XML Schema constaraints this attribute, when present,
3631 * will guaranteedly refer to an existing snapshot definition in XML
3632 */
3633 Guid currentSnapshotId;
3634 CFGLDRQueryUUID (machineNode, "currentSnapshot", currentSnapshotId.ptr());
3635
3636 /* snapshotFolder (optional) */
3637 {
3638 Bstr folder;
3639 CFGLDRQueryBSTR (machineNode, "snapshotFolder", folder.asOutParam());
3640 rc = COMSETTER(SnapshotFolder) (folder);
3641 if (FAILED (rc))
3642 break;
3643 }
3644
3645 /* lastStateChange (optional, for compatiblity) */
3646 {
3647 int64_t lastStateChange = 0;
3648 CFGLDRQueryDateTime (machineNode, "lastStateChange", &lastStateChange);
3649 if (lastStateChange == 0)
3650 {
3651 /// @todo (dmik) until lastStateChange is the required attribute,
3652 // we simply set it to the current time if missing in the config
3653 RTTIMESPEC time;
3654 lastStateChange = RTTimeSpecGetMilli (RTTimeNow (&time));
3655 }
3656 mData->mLastStateChange = lastStateChange;
3657 }
3658
3659 /* aborted (optional) */
3660 bool aborted = false;
3661 CFGLDRQueryBool (machineNode, "aborted", &aborted);
3662
3663 /* currentStateModified (optional, default is true) */
3664 mData->mCurrentStateModified = TRUE;
3665 {
3666 bool val = true;
3667 CFGLDRQueryBool (machineNode, "currentStateModified", &val);
3668 mData->mCurrentStateModified = val;
3669 }
3670
3671 /*
3672 * note: all mUserData members must be assigned prior this point because
3673 * we need to commit changes in order to let mUserData be shared by all
3674 * snapshot machine instances.
3675 */
3676 mUserData.commitCopy();
3677
3678 /* Snapshot node (optional) */
3679 {
3680 CFGNODE snapshotNode = 0;
3681 CFGLDRGetChildNode (machineNode, "Snapshot", 0, &snapshotNode);
3682 if (snapshotNode)
3683 {
3684 /* read all snapshots recursively */
3685 rc = loadSnapshot (snapshotNode, currentSnapshotId, NULL);
3686 CFGLDRReleaseNode (snapshotNode);
3687 if (FAILED (rc))
3688 break;
3689 }
3690 }
3691
3692 /* Hardware node (required) */
3693 {
3694 CFGNODE hardwareNode = 0;
3695 CFGLDRGetChildNode (machineNode, "Hardware", 0, &hardwareNode);
3696 ComAssertBreak (hardwareNode, rc = E_FAIL);
3697 rc = loadHardware (hardwareNode);
3698 CFGLDRReleaseNode (hardwareNode);
3699 if (FAILED (rc))
3700 break;
3701 }
3702
3703 /* HardDiskAttachments node (required) */
3704 {
3705 CFGNODE hdasNode = 0;
3706 CFGLDRGetChildNode (machineNode, "HardDiskAttachments", 0, &hdasNode);
3707 ComAssertBreak (hdasNode, rc = E_FAIL);
3708
3709 rc = loadHardDisks (hdasNode, aRegistered);
3710 CFGLDRReleaseNode (hdasNode);
3711 if (FAILED (rc))
3712 break;
3713 }
3714
3715 /*
3716 * NOTE: the assignment below must be the last thing to do,
3717 * otherwise it will be not possible to change the settings
3718 * somewehere in the code above because all setters will be
3719 * blocked by checkStateDependency (MutableStateDep).
3720 */
3721
3722 /* set the machine state to Aborted or Saved when appropriate */
3723 if (aborted)
3724 {
3725 Assert (!mSSData->mStateFilePath);
3726 mSSData->mStateFilePath.setNull();
3727
3728 /* no need to use setMachineState() during init() */
3729 mData->mMachineState = MachineState_Aborted;
3730 }
3731 else if (mSSData->mStateFilePath)
3732 {
3733 /* no need to use setMachineState() during init() */
3734 mData->mMachineState = MachineState_Saved;
3735 }
3736 }
3737 while (0);
3738
3739 if (machineNode)
3740 CFGLDRReleaseNode (machineNode);
3741
3742 CFGLDRFree (configLoader);
3743
3744 LogFlowThisFuncLeave();
3745 return rc;
3746}
3747
3748/**
3749 * Recursively loads all snapshots starting from the given.
3750 *
3751 * @param aNode <Snapshot> node
3752 * @param aCurSnapshotId current snapshot ID from the settings file
3753 * @param aParentSnapshot parent snapshot
3754 */
3755HRESULT Machine::loadSnapshot (CFGNODE aNode, const Guid &aCurSnapshotId,
3756 Snapshot *aParentSnapshot)
3757{
3758 AssertReturn (aNode, E_INVALIDARG);
3759 AssertReturn (mType == IsMachine, E_FAIL);
3760
3761 // create a snapshot machine object
3762 ComObjPtr <SnapshotMachine> snapshotMachine;
3763 snapshotMachine.createObject();
3764
3765 HRESULT rc = S_OK;
3766
3767 Guid uuid; // required
3768 CFGLDRQueryUUID (aNode, "uuid", uuid.ptr());
3769
3770 Bstr stateFilePath; // optional
3771 CFGLDRQueryBSTR (aNode, "stateFile", stateFilePath.asOutParam());
3772 if (stateFilePath)
3773 {
3774 Utf8Str stateFilePathFull = stateFilePath;
3775 int vrc = calculateFullPath (stateFilePathFull, stateFilePathFull);
3776 if (VBOX_FAILURE (vrc))
3777 return setError (E_FAIL,
3778 tr ("Invalid saved state file path: '%ls' (%Vrc)"),
3779 stateFilePath.raw(), vrc);
3780
3781 stateFilePath = stateFilePathFull;
3782 }
3783
3784 do
3785 {
3786 // Hardware node (required)
3787 CFGNODE hardwareNode = 0;
3788 CFGLDRGetChildNode (aNode, "Hardware", 0, &hardwareNode);
3789 ComAssertBreak (hardwareNode, rc = E_FAIL);
3790
3791 do
3792 {
3793 // HardDiskAttachments node (required)
3794 CFGNODE hdasNode = 0;
3795 CFGLDRGetChildNode (aNode, "HardDiskAttachments", 0, &hdasNode);
3796 ComAssertBreak (hdasNode, rc = E_FAIL);
3797
3798 // initialize the snapshot machine
3799 rc = snapshotMachine->init (this, hardwareNode, hdasNode,
3800 uuid, stateFilePath);
3801
3802 CFGLDRReleaseNode (hdasNode);
3803 }
3804 while (0);
3805
3806 CFGLDRReleaseNode (hardwareNode);
3807 }
3808 while (0);
3809
3810 if (FAILED (rc))
3811 return rc;
3812
3813 // create a snapshot object
3814 ComObjPtr <Snapshot> snapshot;
3815 snapshot.createObject();
3816
3817 {
3818 Bstr name; // required
3819 CFGLDRQueryBSTR (aNode, "name", name.asOutParam());
3820
3821 LONG64 timeStamp = 0; // required
3822 CFGLDRQueryDateTime (aNode, "timeStamp", &timeStamp);
3823
3824 Bstr description; // optional
3825 {
3826 CFGNODE descNode = 0;
3827 CFGLDRGetChildNode (aNode, "Description", 0, &descNode);
3828 if (descNode)
3829 {
3830 CFGLDRQueryBSTR (descNode, NULL, description.asOutParam());
3831 CFGLDRReleaseNode (descNode);
3832 }
3833 }
3834
3835 // initialize the snapshot
3836 rc = snapshot->init (uuid, name, description, timeStamp,
3837 snapshotMachine, aParentSnapshot);
3838 if (FAILED (rc))
3839 return rc;
3840 }
3841
3842 // memorize the first snapshot if necessary
3843 if (!mData->mFirstSnapshot)
3844 mData->mFirstSnapshot = snapshot;
3845
3846 // memorize the current snapshot when appropriate
3847 if (!mData->mCurrentSnapshot && snapshot->data().mId == aCurSnapshotId)
3848 mData->mCurrentSnapshot = snapshot;
3849
3850 // Snapshots node (optional)
3851 {
3852 CFGNODE snapshotsNode = 0;
3853 CFGLDRGetChildNode (aNode, "Snapshots", 0, &snapshotsNode);
3854 if (snapshotsNode)
3855 {
3856 unsigned cbDisks = 0;
3857 CFGLDRCountChildren (snapshotsNode, "Snapshot", &cbDisks);
3858 for (unsigned i = 0; i < cbDisks && SUCCEEDED (rc); i++)
3859 {
3860 CFGNODE snapshotNode;
3861 CFGLDRGetChildNode (snapshotsNode, "Snapshot", i, &snapshotNode);
3862 ComAssertBreak (snapshotNode, rc = E_FAIL);
3863
3864 rc = loadSnapshot (snapshotNode, aCurSnapshotId, snapshot);
3865
3866 CFGLDRReleaseNode (snapshotNode);
3867 }
3868
3869 CFGLDRReleaseNode (snapshotsNode);
3870 }
3871 }
3872
3873 return rc;
3874}
3875
3876/**
3877 * @param aNode <Hardware> node
3878 */
3879HRESULT Machine::loadHardware (CFGNODE aNode)
3880{
3881 AssertReturn (aNode, E_INVALIDARG);
3882 AssertReturn (mType == IsMachine || mType == IsSnapshotMachine, E_FAIL);
3883
3884 /* CPU node (currently not required) */
3885 {
3886 /* default value in case the node is not there */
3887 mHWData->mHWVirtExEnabled = TriStateBool_Default;
3888
3889 CFGNODE cpuNode = 0;
3890 CFGLDRGetChildNode (aNode, "CPU", 0, &cpuNode);
3891 if (cpuNode)
3892 {
3893 CFGNODE hwVirtExNode = 0;
3894 CFGLDRGetChildNode (cpuNode, "HardwareVirtEx", 0, &hwVirtExNode);
3895 if (hwVirtExNode)
3896 {
3897 Bstr hwVirtExEnabled;
3898 CFGLDRQueryBSTR (hwVirtExNode, "enabled", hwVirtExEnabled.asOutParam());
3899 if (hwVirtExEnabled == L"false")
3900 mHWData->mHWVirtExEnabled = TriStateBool_False;
3901 else if (hwVirtExEnabled == L"true")
3902 mHWData->mHWVirtExEnabled = TriStateBool_True;
3903 else
3904 mHWData->mHWVirtExEnabled = TriStateBool_Default;
3905 CFGLDRReleaseNode (hwVirtExNode);
3906 }
3907 CFGLDRReleaseNode (cpuNode);
3908 }
3909 }
3910
3911 /* Memory node (required) */
3912 {
3913 CFGNODE memoryNode = 0;
3914 CFGLDRGetChildNode (aNode, "Memory", 0, &memoryNode);
3915 ComAssertRet (memoryNode, E_FAIL);
3916
3917 uint32_t RAMSize;
3918 CFGLDRQueryUInt32 (memoryNode, "RAMSize", &RAMSize);
3919 mHWData->mMemorySize = RAMSize;
3920 CFGLDRReleaseNode (memoryNode);
3921 }
3922
3923 /* Boot node (required) */
3924 {
3925 /* reset all boot order positions to NoDevice */
3926 for (size_t i = 0; i < ELEMENTS (mHWData->mBootOrder); i++)
3927 mHWData->mBootOrder [i] = DeviceType_NoDevice;
3928
3929 CFGNODE bootNode = 0;
3930 CFGLDRGetChildNode (aNode, "Boot", 0, &bootNode);
3931 ComAssertRet (bootNode, E_FAIL);
3932
3933 HRESULT rc = S_OK;
3934
3935 unsigned cOrder;
3936 CFGLDRCountChildren (bootNode, "Order", &cOrder);
3937 for (unsigned i = 0; i < cOrder; i++)
3938 {
3939 CFGNODE orderNode = 0;
3940 CFGLDRGetChildNode (bootNode, "Order", i, &orderNode);
3941 ComAssertBreak (orderNode, rc = E_FAIL);
3942
3943 /* position (required) */
3944 /* position unicity is guaranteed by XML Schema */
3945 uint32_t position = 0;
3946 CFGLDRQueryUInt32 (orderNode, "position", &position);
3947 -- position;
3948 Assert (position < ELEMENTS (mHWData->mBootOrder));
3949
3950 /* device (required) */
3951 Bstr device;
3952 CFGLDRQueryBSTR (orderNode, "device", device.asOutParam());
3953 if (device == L"None")
3954 mHWData->mBootOrder [position] = DeviceType_NoDevice;
3955 else if (device == L"Floppy")
3956 mHWData->mBootOrder [position] = DeviceType_FloppyDevice;
3957 else if (device == L"DVD")
3958 mHWData->mBootOrder [position] = DeviceType_DVDDevice;
3959 else if (device == L"HardDisk")
3960 mHWData->mBootOrder [position] = DeviceType_HardDiskDevice;
3961 else if (device == L"Network")
3962 mHWData->mBootOrder [position] = DeviceType_NetworkDevice;
3963 else
3964 ComAssertMsgFailed (("Invalid device: %ls\n", device.raw()));
3965
3966 CFGLDRReleaseNode (orderNode);
3967 }
3968
3969 CFGLDRReleaseNode (bootNode);
3970 if (FAILED (rc))
3971 return rc;
3972 }
3973
3974 /* Display node (required) */
3975 {
3976 CFGNODE displayNode = 0;
3977 CFGLDRGetChildNode (aNode, "Display", 0, &displayNode);
3978 ComAssertRet (displayNode, E_FAIL);
3979
3980 uint32_t VRAMSize;
3981 CFGLDRQueryUInt32 (displayNode, "VRAMSize", &VRAMSize);
3982 mHWData->mVRAMSize = VRAMSize;
3983
3984 uint32_t MonitorCount;
3985 CFGLDRQueryUInt32 (displayNode, "MonitorCount", &MonitorCount);
3986 mHWData->mMonitorCount = MonitorCount;
3987
3988 CFGLDRReleaseNode (displayNode);
3989 }
3990
3991#ifdef VBOX_VRDP
3992 /* RemoteDisplay node (optional) */
3993 {
3994 CFGNODE remoteDisplayNode = 0;
3995 CFGLDRGetChildNode (aNode, "RemoteDisplay", 0, &remoteDisplayNode);
3996 if (remoteDisplayNode)
3997 {
3998 HRESULT rc = mVRDPServer->loadSettings (remoteDisplayNode);
3999 CFGLDRReleaseNode (remoteDisplayNode);
4000 CheckComRCReturnRC (rc);
4001 }
4002 }
4003#endif
4004
4005 /* BIOS node (required) */
4006 {
4007 CFGNODE biosNode = 0;
4008 CFGLDRGetChildNode (aNode, "BIOS", 0, &biosNode);
4009 ComAssertRet (biosNode, E_FAIL);
4010
4011 HRESULT rc = S_OK;
4012
4013 do
4014 {
4015 /* ACPI */
4016 {
4017 CFGNODE acpiNode = 0;
4018 CFGLDRGetChildNode (biosNode, "ACPI", 0, &acpiNode);
4019 ComAssertBreak (acpiNode, rc = E_FAIL);
4020
4021 bool enabled;
4022 CFGLDRQueryBool (acpiNode, "enabled", &enabled);
4023 mBIOSSettings->COMSETTER(ACPIEnabled)(enabled);
4024 CFGLDRReleaseNode (acpiNode);
4025 }
4026
4027 /* IOAPIC */
4028 {
4029 CFGNODE ioapicNode = 0;
4030 CFGLDRGetChildNode (biosNode, "IOAPIC", 0, &ioapicNode);
4031 if (ioapicNode)
4032 {
4033 bool enabled;
4034 CFGLDRQueryBool (ioapicNode, "enabled", &enabled);
4035 mBIOSSettings->COMSETTER(IOAPICEnabled)(enabled);
4036 CFGLDRReleaseNode (ioapicNode);
4037 }
4038 }
4039
4040 /* Logo (optional) */
4041 {
4042 CFGNODE logoNode = 0;
4043 CFGLDRGetChildNode (biosNode, "Logo", 0, &logoNode);
4044 if (logoNode)
4045 {
4046 bool enabled = false;
4047 CFGLDRQueryBool (logoNode, "fadeIn", &enabled);
4048 mBIOSSettings->COMSETTER(LogoFadeIn)(enabled);
4049 CFGLDRQueryBool (logoNode, "fadeOut", &enabled);
4050 mBIOSSettings->COMSETTER(LogoFadeOut)(enabled);
4051
4052 uint32_t BIOSLogoDisplayTime;
4053 CFGLDRQueryUInt32 (logoNode, "displayTime", &BIOSLogoDisplayTime);
4054 mBIOSSettings->COMSETTER(LogoDisplayTime)(BIOSLogoDisplayTime);
4055
4056 Bstr logoPath;
4057 CFGLDRQueryBSTR (logoNode, "imagePath", logoPath.asOutParam());
4058 mBIOSSettings->COMSETTER(LogoImagePath)(logoPath);
4059
4060 CFGLDRReleaseNode (logoNode);
4061 }
4062 }
4063
4064 /* boot menu (optional) */
4065 {
4066 CFGNODE bootMenuNode = 0;
4067 CFGLDRGetChildNode (biosNode, "BootMenu", 0, &bootMenuNode);
4068 if (bootMenuNode)
4069 {
4070 Bstr modeStr;
4071 BIOSBootMenuMode_T mode;
4072 CFGLDRQueryBSTR (bootMenuNode, "mode", modeStr.asOutParam());
4073 if (modeStr == L"disabled")
4074 mode = BIOSBootMenuMode_Disabled;
4075 else if (modeStr == L"menuonly")
4076 mode = BIOSBootMenuMode_MenuOnly;
4077 else
4078 mode = BIOSBootMenuMode_MessageAndMenu;
4079 mBIOSSettings->COMSETTER(BootMenuMode)(mode);
4080
4081 CFGLDRReleaseNode (bootMenuNode);
4082 }
4083 }
4084
4085 /* time offset (optional) */
4086 {
4087 CFGNODE timeOffsetNode = 0;
4088 CFGLDRGetChildNode (biosNode, "TimeOffset", 0, &timeOffsetNode);
4089 if (timeOffsetNode)
4090 {
4091 LONG64 timeOffset;
4092 CFGLDRQueryInt64 (timeOffsetNode, "value", &timeOffset);
4093 mBIOSSettings->COMSETTER(TimeOffset)(timeOffset);
4094 CFGLDRReleaseNode (timeOffsetNode);
4095 }
4096 }
4097 }
4098 while (0);
4099
4100 CFGLDRReleaseNode (biosNode);
4101 if (FAILED (rc))
4102 return rc;
4103 }
4104
4105 /* DVD drive (contains either Image or HostDrive or nothing) */
4106 /// @todo (dmik) move the code to DVDDrive
4107 {
4108 HRESULT rc = S_OK;
4109
4110 CFGNODE dvdDriveNode = 0;
4111 CFGLDRGetChildNode (aNode, "DVDDrive", 0, &dvdDriveNode);
4112 ComAssertRet (dvdDriveNode, E_FAIL);
4113
4114 bool fPassthrough;
4115 CFGLDRQueryBool(dvdDriveNode, "passthrough", &fPassthrough);
4116 mDVDDrive->COMSETTER(Passthrough)(fPassthrough);
4117
4118 CFGNODE typeNode = 0;
4119
4120 do
4121 {
4122 CFGLDRGetChildNode (dvdDriveNode, "Image", 0, &typeNode);
4123 if (typeNode)
4124 {
4125 Guid uuid;
4126 CFGLDRQueryUUID (typeNode, "uuid", uuid.ptr());
4127 rc = mDVDDrive->MountImage (uuid);
4128 }
4129 else
4130 {
4131 CFGLDRGetChildNode (dvdDriveNode, "HostDrive", 0, &typeNode);
4132 if (typeNode)
4133 {
4134 Bstr src;
4135 CFGLDRQueryBSTR (typeNode, "src", src.asOutParam());
4136
4137 /* find the correspoding object */
4138 ComPtr <IHost> host;
4139 rc = mParent->COMGETTER(Host) (host.asOutParam());
4140 ComAssertComRCBreak (rc, rc = rc);
4141
4142 ComPtr <IHostDVDDriveCollection> coll;
4143 rc = host->COMGETTER(DVDDrives) (coll.asOutParam());
4144 ComAssertComRCBreak (rc, rc = rc);
4145
4146 ComPtr <IHostDVDDrive> drive;
4147 rc = coll->FindByName (src, drive.asOutParam());
4148 if (SUCCEEDED (rc))
4149 rc = mDVDDrive->CaptureHostDrive (drive);
4150 else if (rc == E_INVALIDARG)
4151 {
4152 /* the host DVD drive is not currently available. we
4153 * assume it will be available later and create an
4154 * extra object now */
4155 ComObjPtr <HostDVDDrive> hostDrive;
4156 hostDrive.createObject();
4157 rc = hostDrive->init (src);
4158 ComAssertComRCBreak (rc, rc = rc);
4159 rc = mDVDDrive->CaptureHostDrive (hostDrive);
4160 }
4161 else
4162 ComAssertComRCBreak (rc, rc = rc);
4163 }
4164 }
4165 }
4166 while (0);
4167
4168 if (typeNode)
4169 CFGLDRReleaseNode (typeNode);
4170 CFGLDRReleaseNode (dvdDriveNode);
4171
4172 if (FAILED (rc))
4173 return rc;
4174 }
4175
4176 /* Floppy drive (contains either Image or HostDrive or nothing) */
4177 /// @todo (dmik) move the code to FloppyDrive
4178 {
4179 HRESULT rc = S_OK;
4180
4181 CFGNODE driveNode = 0;
4182 CFGLDRGetChildNode (aNode, "FloppyDrive", 0, &driveNode);
4183 ComAssertRet (driveNode, E_FAIL);
4184
4185 BOOL fFloppyEnabled = TRUE;
4186 CFGLDRQueryBool (driveNode, "enabled", (bool*)&fFloppyEnabled);
4187 rc = mFloppyDrive->COMSETTER(Enabled)(fFloppyEnabled);
4188
4189 CFGNODE typeNode = 0;
4190 do
4191 {
4192 CFGLDRGetChildNode (driveNode, "Image", 0, &typeNode);
4193 if (typeNode)
4194 {
4195 Guid uuid;
4196 CFGLDRQueryUUID (typeNode, "uuid", uuid.ptr());
4197 rc = mFloppyDrive->MountImage (uuid);
4198 }
4199 else
4200 {
4201 CFGLDRGetChildNode (driveNode, "HostDrive", 0, &typeNode);
4202 if (typeNode)
4203 {
4204 Bstr src;
4205 CFGLDRQueryBSTR (typeNode, "src", src.asOutParam());
4206
4207 /* find the correspoding object */
4208 ComPtr <IHost> host;
4209 rc = mParent->COMGETTER(Host) (host.asOutParam());
4210 ComAssertComRCBreak (rc, rc = rc);
4211
4212 ComPtr <IHostFloppyDriveCollection> coll;
4213 rc = host->COMGETTER(FloppyDrives) (coll.asOutParam());
4214 ComAssertComRCBreak (rc, rc = rc);
4215
4216 ComPtr <IHostFloppyDrive> drive;
4217 rc = coll->FindByName (src, drive.asOutParam());
4218 if (SUCCEEDED (rc))
4219 rc = mFloppyDrive->CaptureHostDrive (drive);
4220 else if (rc == E_INVALIDARG)
4221 {
4222 /* the host Floppy drive is not currently available. we
4223 * assume it will be available later and create an
4224 * extra object now */
4225 ComObjPtr <HostFloppyDrive> hostDrive;
4226 hostDrive.createObject();
4227 rc = hostDrive->init (src);
4228 ComAssertComRCBreak (rc, rc = rc);
4229 rc = mFloppyDrive->CaptureHostDrive (hostDrive);
4230 }
4231 else
4232 ComAssertComRCBreak (rc, rc = rc);
4233 }
4234 }
4235 }
4236 while (0);
4237
4238 if (typeNode)
4239 CFGLDRReleaseNode (typeNode);
4240 CFGLDRReleaseNode (driveNode);
4241
4242 CheckComRCReturnRC (rc);
4243 }
4244
4245 /* USB Controller */
4246 {
4247 HRESULT rc = mUSBController->loadSettings (aNode);
4248 CheckComRCReturnRC (rc);
4249 }
4250
4251 /* Network node (required) */
4252 /// @todo (dmik) move the code to NetworkAdapter
4253 {
4254 /* we assume that all network adapters are initially disabled
4255 * and detached */
4256
4257 CFGNODE networkNode = 0;
4258 CFGLDRGetChildNode (aNode, "Network", 0, &networkNode);
4259 ComAssertRet (networkNode, E_FAIL);
4260
4261 HRESULT rc = S_OK;
4262
4263 unsigned cAdapters = 0;
4264 CFGLDRCountChildren (networkNode, "Adapter", &cAdapters);
4265 for (unsigned i = 0; i < cAdapters; i++)
4266 {
4267 CFGNODE adapterNode = 0;
4268 CFGLDRGetChildNode (networkNode, "Adapter", i, &adapterNode);
4269 ComAssertBreak (adapterNode, rc = E_FAIL);
4270
4271 /* slot number (required) */
4272 /* slot unicity is guaranteed by XML Schema */
4273 uint32_t slot = 0;
4274 CFGLDRQueryUInt32 (adapterNode, "slot", &slot);
4275 Assert (slot < ELEMENTS (mNetworkAdapters));
4276
4277 /* type */
4278 Bstr adapterType;
4279 CFGLDRQueryBSTR (adapterNode, "type", adapterType.asOutParam());
4280 ComAssertBreak (adapterType, rc = E_FAIL);
4281
4282 /* enabled (required) */
4283 bool enabled = false;
4284 CFGLDRQueryBool (adapterNode, "enabled", &enabled);
4285 /* MAC address (can be null) */
4286 Bstr macAddr;
4287 CFGLDRQueryBSTR (adapterNode, "MACAddress", macAddr.asOutParam());
4288 /* cable (required) */
4289 bool cableConnected;
4290 CFGLDRQueryBool (adapterNode, "cable", &cableConnected);
4291 /* tracing (defaults to false) */
4292 bool traceEnabled;
4293 CFGLDRQueryBool (adapterNode, "trace", &traceEnabled);
4294 Bstr traceFile;
4295 CFGLDRQueryBSTR (adapterNode, "tracefile", traceFile.asOutParam());
4296
4297 mNetworkAdapters [slot]->COMSETTER(Enabled) (enabled);
4298 mNetworkAdapters [slot]->COMSETTER(MACAddress) (macAddr);
4299 mNetworkAdapters [slot]->COMSETTER(CableConnected) (cableConnected);
4300 mNetworkAdapters [slot]->COMSETTER(TraceEnabled) (traceEnabled);
4301 mNetworkAdapters [slot]->COMSETTER(TraceFile) (traceFile);
4302
4303 if (adapterType.compare(Bstr("Am79C970A")) == 0)
4304 mNetworkAdapters [slot]->COMSETTER(AdapterType)(NetworkAdapterType_NetworkAdapterAm79C970A);
4305 else if (adapterType.compare(Bstr("Am79C973")) == 0)
4306 mNetworkAdapters [slot]->COMSETTER(AdapterType)(NetworkAdapterType_NetworkAdapterAm79C973);
4307 else
4308 ComAssertBreak (0, rc = E_FAIL);
4309
4310 CFGNODE attachmentNode = 0;
4311 if (CFGLDRGetChildNode (adapterNode, "NAT", 0, &attachmentNode), attachmentNode)
4312 {
4313 mNetworkAdapters [slot]->AttachToNAT();
4314 }
4315 else
4316 if (CFGLDRGetChildNode (adapterNode, "HostInterface", 0, &attachmentNode), attachmentNode)
4317 {
4318 /* Host Interface Networking */
4319 Bstr name;
4320 CFGLDRQueryBSTR (attachmentNode, "name", name.asOutParam());
4321#ifdef RT_OS_WINDOWS
4322 /* @name can be empty on Win32, but not null */
4323 ComAssertBreak (!name.isNull(), rc = E_FAIL);
4324#endif
4325 mNetworkAdapters [slot]->COMSETTER(HostInterface) (name);
4326#ifdef VBOX_WITH_UNIXY_TAP_NETWORKING
4327 Bstr tapSetupApp;
4328 CFGLDRQueryBSTR (attachmentNode, "TAPSetup", tapSetupApp.asOutParam());
4329 Bstr tapTerminateApp;
4330 CFGLDRQueryBSTR (attachmentNode, "TAPTerminate", tapTerminateApp.asOutParam());
4331
4332 mNetworkAdapters [slot]->COMSETTER(TAPSetupApplication) (tapSetupApp);
4333 mNetworkAdapters [slot]->COMSETTER(TAPTerminateApplication) (tapTerminateApp);
4334#endif // VBOX_WITH_UNIXY_TAP_NETWORKING
4335 mNetworkAdapters [slot]->AttachToHostInterface();
4336 }
4337 else
4338 if (CFGLDRGetChildNode(adapterNode, "InternalNetwork", 0, &attachmentNode), attachmentNode)
4339 {
4340 /* Internal Networking */
4341 Bstr name;
4342 CFGLDRQueryBSTR (attachmentNode, "name", name.asOutParam());
4343 ComAssertBreak (!name.isNull(), rc = E_FAIL);
4344 mNetworkAdapters[slot]->AttachToInternalNetwork();
4345 mNetworkAdapters[slot]->COMSETTER(InternalNetwork) (name);
4346 }
4347 else
4348 {
4349 /* Adapter has no children */
4350 mNetworkAdapters [slot]->Detach();
4351 }
4352 if (attachmentNode)
4353 CFGLDRReleaseNode (attachmentNode);
4354
4355 CFGLDRReleaseNode (adapterNode);
4356 }
4357
4358 CFGLDRReleaseNode (networkNode);
4359 if (FAILED (rc))
4360 return rc;
4361 }
4362
4363 /* Serial node (optional) */
4364 CFGNODE serialNode = 0;
4365 CFGLDRGetChildNode (aNode, "Uart", 0, &serialNode);
4366 if (serialNode)
4367 {
4368 HRESULT rc = S_OK;
4369 unsigned cPorts = 0;
4370 CFGLDRCountChildren (serialNode, "Port", &cPorts);
4371 for (unsigned slot = 0; slot < cPorts; slot++)
4372 {
4373 rc = mSerialPorts [slot]->loadSettings (serialNode, slot);
4374 CheckComRCReturnRC (rc);
4375 }
4376 CFGLDRReleaseNode (serialNode);
4377 }
4378
4379 /* Parallel node (optional) */
4380 CFGNODE parallelNode = 0;
4381 CFGLDRGetChildNode (aNode, "Lpt", 0, &parallelNode);
4382 if (parallelNode)
4383 {
4384 HRESULT rc = S_OK;
4385 unsigned cPorts = 0;
4386 CFGLDRCountChildren (parallelNode, "Port", &cPorts);
4387 for (unsigned slot = 0; slot < cPorts; slot++)
4388 {
4389 rc = mParallelPorts [slot]->loadSettings (parallelNode, slot);
4390 CheckComRCReturnRC (rc);
4391 }
4392 CFGLDRReleaseNode (parallelNode);
4393 }
4394
4395 /* AudioAdapter node (required) */
4396 /// @todo (dmik) move the code to AudioAdapter
4397 {
4398 CFGNODE audioAdapterNode = 0;
4399 CFGLDRGetChildNode (aNode, "AudioAdapter", 0, &audioAdapterNode);
4400 ComAssertRet (audioAdapterNode, E_FAIL);
4401
4402 // is the adapter enabled?
4403 bool enabled = false;
4404 CFGLDRQueryBool (audioAdapterNode, "enabled", &enabled);
4405 mAudioAdapter->COMSETTER(Enabled) (enabled);
4406 // now check the audio driver
4407 Bstr driver;
4408 CFGLDRQueryBSTR (audioAdapterNode, "driver", driver.asOutParam());
4409 AudioDriverType_T audioDriver;
4410 audioDriver = AudioDriverType_NullAudioDriver;
4411 if (driver == L"null")
4412 ; // Null has been set above
4413#ifdef RT_OS_WINDOWS
4414 else if (driver == L"winmm")
4415#ifdef VBOX_WITH_WINMM
4416 audioDriver = AudioDriverType_WINMMAudioDriver;
4417#else
4418 // fall back to dsound
4419 audioDriver = AudioDriverType_DSOUNDAudioDriver;
4420#endif
4421 else if (driver == L"dsound")
4422 audioDriver = AudioDriverType_DSOUNDAudioDriver;
4423#endif // RT_OS_WINDOWS
4424#ifdef RT_OS_LINUX
4425 else if (driver == L"oss")
4426 audioDriver = AudioDriverType_OSSAudioDriver;
4427 else if (driver == L"alsa")
4428#ifdef VBOX_WITH_ALSA
4429 audioDriver = AudioDriverType_ALSAAudioDriver;
4430#else
4431 // fall back to OSS
4432 audioDriver = AudioDriverType_OSSAudioDriver;
4433#endif
4434#endif // RT_OS_LINUX
4435#ifdef RT_OS_DARWIN
4436 else if (driver == L"coreaudio")
4437 audioDriver = AudioDriverType_CoreAudioDriver;
4438#endif
4439#ifdef RT_OS_OS2
4440 else if (driver == L"mmpm")
4441 audioDriver = AudioDriverType_MMPMAudioDriver;
4442#endif
4443 else
4444 AssertMsgFailed (("Invalid driver: %ls\n", driver.raw()));
4445 mAudioAdapter->COMSETTER(AudioDriver) (audioDriver);
4446
4447 CFGLDRReleaseNode (audioAdapterNode);
4448 }
4449
4450 /* Shared folders (optional) */
4451 /// @todo (dmik) make required on next format change!
4452 do
4453 {
4454 CFGNODE sharedFoldersNode = 0;
4455 CFGLDRGetChildNode (aNode, "SharedFolders", 0, &sharedFoldersNode);
4456
4457 if (!sharedFoldersNode)
4458 break;
4459
4460 HRESULT rc = S_OK;
4461
4462 unsigned cFolders = 0;
4463 CFGLDRCountChildren (sharedFoldersNode, "SharedFolder", &cFolders);
4464
4465 for (unsigned i = 0; i < cFolders; i++)
4466 {
4467 CFGNODE folderNode = 0;
4468 CFGLDRGetChildNode (sharedFoldersNode, "SharedFolder", i, &folderNode);
4469 ComAssertBreak (folderNode, rc = E_FAIL);
4470
4471 // folder logical name (required)
4472 Bstr name;
4473 CFGLDRQueryBSTR (folderNode, "name", name.asOutParam());
4474
4475 // folder host path (required)
4476 Bstr hostPath;
4477 CFGLDRQueryBSTR (folderNode, "hostPath", hostPath.asOutParam());
4478
4479 rc = CreateSharedFolder (name, hostPath);
4480 if (FAILED (rc))
4481 break;
4482
4483 CFGLDRReleaseNode (folderNode);
4484 }
4485
4486 CFGLDRReleaseNode (sharedFoldersNode);
4487 if (FAILED (rc))
4488 return rc;
4489 }
4490 while (0);
4491
4492 /* Clipboard node (currently not required) */
4493 /// @todo (dmik) make required on next format change!
4494 {
4495 /* default value in case the node is not there */
4496 mHWData->mClipboardMode = ClipboardMode_ClipDisabled;
4497
4498 CFGNODE clipNode = 0;
4499 CFGLDRGetChildNode (aNode, "Clipboard", 0, &clipNode);
4500 if (clipNode)
4501 {
4502 Bstr mode;
4503 CFGLDRQueryBSTR (clipNode, "mode", mode.asOutParam());
4504 if (mode == L"Disabled")
4505 mHWData->mClipboardMode = ClipboardMode_ClipDisabled;
4506 else if (mode == L"HostToGuest")
4507 mHWData->mClipboardMode = ClipboardMode_ClipHostToGuest;
4508 else if (mode == L"GuestToHost")
4509 mHWData->mClipboardMode = ClipboardMode_ClipGuestToHost;
4510 else if (mode == L"Bidirectional")
4511 mHWData->mClipboardMode = ClipboardMode_ClipBidirectional;
4512 else
4513 AssertMsgFailed (("%ls clipboard mode is invalid\n", mode.raw()));
4514 CFGLDRReleaseNode (clipNode);
4515 }
4516 }
4517
4518 return S_OK;
4519}
4520
4521/**
4522 * @param aNode <HardDiskAttachments> node
4523 * @param aRegistered true when the machine is being loaded on VirtualBox
4524 * startup, or when a snapshot is being loaded (wchich
4525 * currently can happen on startup only)
4526 * @param aSnapshotId pointer to the snapshot ID if this is a snapshot machine
4527 */
4528HRESULT Machine::loadHardDisks (CFGNODE aNode, bool aRegistered,
4529 const Guid *aSnapshotId /* = NULL */)
4530{
4531 AssertReturn (aNode, E_INVALIDARG);
4532 AssertReturn ((mType == IsMachine && aSnapshotId == NULL) ||
4533 (mType == IsSnapshotMachine && aSnapshotId != NULL), E_FAIL);
4534
4535 HRESULT rc = S_OK;
4536
4537 unsigned cbDisks = 0;
4538 CFGLDRCountChildren (aNode, "HardDiskAttachment", &cbDisks);
4539
4540 if (!aRegistered && cbDisks > 0)
4541 {
4542 /* when the machine is being loaded (opened) from a file, it cannot
4543 * have hard disks attached (this should not happen normally,
4544 * because we don't allow to attach hard disks to an unregistered
4545 * VM at all */
4546 return setError (E_FAIL,
4547 tr ("Unregistered machine '%ls' cannot have hard disks attached "
4548 "(found %d hard disk attachments)"),
4549 mUserData->mName.raw(), cbDisks);
4550 }
4551
4552 for (unsigned i = 0; i < cbDisks && SUCCEEDED (rc); ++ i)
4553 {
4554 CFGNODE hdNode;
4555 CFGLDRGetChildNode (aNode, "HardDiskAttachment", i, &hdNode);
4556 ComAssertRet (hdNode, E_FAIL);
4557
4558 do
4559 {
4560 /* hardDisk uuid (required) */
4561 Guid uuid;
4562 CFGLDRQueryUUID (hdNode, "hardDisk", uuid.ptr());
4563 /* bus (controller) type (required) */
4564 Bstr bus;
4565 CFGLDRQueryBSTR (hdNode, "bus", bus.asOutParam());
4566 /* device (required) */
4567 Bstr device;
4568 CFGLDRQueryBSTR (hdNode, "device", device.asOutParam());
4569
4570 /* find a hard disk by UUID */
4571 ComObjPtr <HardDisk> hd;
4572 rc = mParent->getHardDisk (uuid, hd);
4573 if (FAILED (rc))
4574 break;
4575
4576 AutoLock hdLock (hd);
4577
4578 if (!hd->machineId().isEmpty())
4579 {
4580 rc = setError (E_FAIL,
4581 tr ("Hard disk '%ls' with UUID {%s} is already "
4582 "attached to a machine with UUID {%s} (see '%ls')"),
4583 hd->toString().raw(), uuid.toString().raw(),
4584 hd->machineId().toString().raw(),
4585 mData->mConfigFileFull.raw());
4586 break;
4587 }
4588
4589 if (hd->type() == HardDiskType_ImmutableHardDisk)
4590 {
4591 rc = setError (E_FAIL,
4592 tr ("Immutable hard disk '%ls' with UUID {%s} cannot be "
4593 "directly attached to a machine (see '%ls')"),
4594 hd->toString().raw(), uuid.toString().raw(),
4595 mData->mConfigFileFull.raw());
4596 break;
4597 }
4598
4599 /* attach the device */
4600 DiskControllerType_T ctl = DiskControllerType_InvalidController;
4601 LONG dev = -1;
4602
4603 if (bus == L"ide0")
4604 {
4605 ctl = DiskControllerType_IDE0Controller;
4606 if (device == L"master")
4607 dev = 0;
4608 else if (device == L"slave")
4609 dev = 1;
4610 else
4611 ComAssertMsgFailedBreak (("Invalid device: %ls\n", device.raw()),
4612 rc = E_FAIL);
4613 }
4614 else if (bus == L"ide1")
4615 {
4616 ctl = DiskControllerType_IDE1Controller;
4617 if (device == L"master")
4618 rc = setError (E_FAIL, tr("Could not attach a disk as a master "
4619 "device on the secondary controller"));
4620 else if (device == L"slave")
4621 dev = 1;
4622 else
4623 ComAssertMsgFailedBreak (("Invalid device: %ls\n", device.raw()),
4624 rc = E_FAIL);
4625 }
4626 else
4627 ComAssertMsgFailedBreak (("Invalid bus: %ls\n", bus.raw()),
4628 rc = E_FAIL);
4629
4630 ComObjPtr <HardDiskAttachment> attachment;
4631 attachment.createObject();
4632 rc = attachment->init (hd, ctl, dev, false /* aDirty */);
4633 if (FAILED (rc))
4634 break;
4635
4636 /* associate the hard disk with this machine */
4637 hd->setMachineId (mData->mUuid);
4638
4639 /* associate the hard disk with the given snapshot ID */
4640 if (mType == IsSnapshotMachine)
4641 hd->setSnapshotId (*aSnapshotId);
4642
4643 mHDData->mHDAttachments.push_back (attachment);
4644 }
4645 while (0);
4646
4647 CFGLDRReleaseNode (hdNode);
4648 }
4649
4650 return rc;
4651}
4652
4653/**
4654 * Creates a config loader and loads the settings file.
4655 *
4656 * @param aIsNew |true| if a newly created settings file is to be opened
4657 * (must be the case only when called from #saveSettings())
4658 *
4659 * @note
4660 * XML Schema errors are not detected by this method because
4661 * it assumes that it will load settings from an exclusively locked
4662 * file (using a file handle) that was previously validated when opened
4663 * for the first time. Thus, this method should be used only when
4664 * it's necessary to modify (save) the settings file.
4665 *
4666 * @note The object must be locked at least for reading before calling
4667 * this method.
4668 */
4669HRESULT Machine::openConfigLoader (CFGHANDLE *aLoader, bool aIsNew /* = false */)
4670{
4671 AssertReturn (aLoader, E_FAIL);
4672
4673 /* The settings file must be created and locked at this point */
4674 ComAssertRet (isConfigLocked(), E_FAIL);
4675
4676 /* load the config file */
4677 int vrc = CFGLDRLoad (aLoader,
4678 Utf8Str (mData->mConfigFileFull), mData->mHandleCfgFile,
4679 aIsNew ? NULL : XmlSchemaNS, true, cfgLdrEntityResolver,
4680 NULL);
4681 ComAssertRCRet (vrc, E_FAIL);
4682
4683 return S_OK;
4684}
4685
4686/**
4687 * Closes the config loader previously created by #openConfigLoader().
4688 * If \a aSaveBeforeClose is true, then the config is saved to the settings file
4689 * before closing. If saving fails, a proper error message is set.
4690 *
4691 * @param aSaveBeforeClose whether to save the config before closing or not
4692 */
4693HRESULT Machine::closeConfigLoader (CFGHANDLE aLoader, bool aSaveBeforeClose)
4694{
4695 HRESULT rc = S_OK;
4696
4697 if (aSaveBeforeClose)
4698 {
4699 char *loaderError = NULL;
4700 int vrc = CFGLDRSave (aLoader, &loaderError);
4701 if (VBOX_FAILURE (vrc))
4702 {
4703 rc = setError (E_FAIL,
4704 tr ("Could not save the settings file '%ls' (%Vrc)%s%s"),
4705 mData->mConfigFileFull.raw(), vrc,
4706 loaderError ? ".\n" : "", loaderError ? loaderError : "");
4707 if (loaderError)
4708 RTMemTmpFree (loaderError);
4709 }
4710 }
4711
4712 CFGLDRFree (aLoader);
4713
4714 return rc;
4715}
4716
4717/**
4718 * Searches for a <Snapshot> node for the given snapshot.
4719 * If the search is successful, \a aSnapshotNode will contain the found node.
4720 * In this case, \a aSnapshotsNode can be NULL meaning the found node is a
4721 * direct child of \a aMachineNode.
4722 *
4723 * If the search fails, a failure is returned and both \a aSnapshotsNode and
4724 * \a aSnapshotNode are set to 0.
4725 *
4726 * @param aSnapshot snapshot to search for
4727 * @param aMachineNode <Machine> node to start from
4728 * @param aSnapshotsNode <Snapshots> node containing the found <Snapshot> node
4729 * (may be NULL if the caller is not interested)
4730 * @param aSnapshotNode found <Snapshot> node
4731 */
4732HRESULT Machine::findSnapshotNode (Snapshot *aSnapshot, CFGNODE aMachineNode,
4733 CFGNODE *aSnapshotsNode, CFGNODE *aSnapshotNode)
4734{
4735 AssertReturn (aSnapshot && aMachineNode && aSnapshotNode, E_FAIL);
4736
4737 if (aSnapshotsNode)
4738 *aSnapshotsNode = 0;
4739 *aSnapshotNode = 0;
4740
4741 // build the full uuid path (from the fist parent to the given snapshot)
4742 std::list <Guid> path;
4743 {
4744 ComObjPtr <Snapshot> parent = aSnapshot;
4745 while (parent)
4746 {
4747 path.push_front (parent->data().mId);
4748 parent = parent->parent();
4749 }
4750 }
4751
4752 CFGNODE snapshotsNode = aMachineNode;
4753 CFGNODE snapshotNode = 0;
4754
4755 for (std::list <Guid>::const_iterator it = path.begin();
4756 it != path.end();
4757 ++ it)
4758 {
4759 if (snapshotNode)
4760 {
4761 // proceed to the nested <Snapshots> node
4762 Assert (snapshotsNode);
4763 if (snapshotsNode != aMachineNode)
4764 {
4765 CFGLDRReleaseNode (snapshotsNode);
4766 snapshotsNode = 0;
4767 }
4768 CFGLDRGetChildNode (snapshotNode, "Snapshots", 0, &snapshotsNode);
4769 CFGLDRReleaseNode (snapshotNode);
4770 snapshotNode = 0;
4771 }
4772
4773 AssertReturn (snapshotsNode, E_FAIL);
4774
4775 unsigned count = 0, i = 0;
4776 CFGLDRCountChildren (snapshotsNode, "Snapshot", &count);
4777 for (; i < count; ++ i)
4778 {
4779 snapshotNode = 0;
4780 CFGLDRGetChildNode (snapshotsNode, "Snapshot", i, &snapshotNode);
4781 Guid id;
4782 CFGLDRQueryUUID (snapshotNode, "uuid", id.ptr());
4783 if (id == (*it))
4784 {
4785 // we keep (don't release) snapshotNode and snapshotsNode
4786 break;
4787 }
4788 CFGLDRReleaseNode (snapshotNode);
4789 snapshotNode = 0;
4790 }
4791
4792 if (i == count)
4793 {
4794 // the next uuid is not found, no need to continue...
4795 AssertFailed();
4796 if (snapshotsNode != aMachineNode)
4797 {
4798 CFGLDRReleaseNode (snapshotsNode);
4799 snapshotsNode = 0;
4800 }
4801 break;
4802 }
4803 }
4804
4805 // we must always succesfully find the node
4806 AssertReturn (snapshotNode, E_FAIL);
4807 AssertReturn (snapshotsNode, E_FAIL);
4808
4809 if (aSnapshotsNode)
4810 *aSnapshotsNode = snapshotsNode != aMachineNode ? snapshotsNode : 0;
4811 *aSnapshotNode = snapshotNode;
4812
4813 return S_OK;
4814}
4815
4816/**
4817 * Returns the snapshot with the given UUID or fails of no such snapshot.
4818 *
4819 * @param aId snapshot UUID to find (empty UUID refers the first snapshot)
4820 * @param aSnapshot where to return the found snapshot
4821 * @param aSetError true to set extended error info on failure
4822 */
4823HRESULT Machine::findSnapshot (const Guid &aId, ComObjPtr <Snapshot> &aSnapshot,
4824 bool aSetError /* = false */)
4825{
4826 if (!mData->mFirstSnapshot)
4827 {
4828 if (aSetError)
4829 return setError (E_FAIL,
4830 tr ("This machine does not have any snapshots"));
4831 return E_FAIL;
4832 }
4833
4834 if (aId.isEmpty())
4835 aSnapshot = mData->mFirstSnapshot;
4836 else
4837 aSnapshot = mData->mFirstSnapshot->findChildOrSelf (aId);
4838
4839 if (!aSnapshot)
4840 {
4841 if (aSetError)
4842 return setError (E_FAIL,
4843 tr ("Could not find a snapshot with UUID {%s}"),
4844 aId.toString().raw());
4845 return E_FAIL;
4846 }
4847
4848 return S_OK;
4849}
4850
4851/**
4852 * Returns the snapshot with the given name or fails of no such snapshot.
4853 *
4854 * @param aName snapshot name to find
4855 * @param aSnapshot where to return the found snapshot
4856 * @param aSetError true to set extended error info on failure
4857 */
4858HRESULT Machine::findSnapshot (const BSTR aName, ComObjPtr <Snapshot> &aSnapshot,
4859 bool aSetError /* = false */)
4860{
4861 AssertReturn (aName, E_INVALIDARG);
4862
4863 if (!mData->mFirstSnapshot)
4864 {
4865 if (aSetError)
4866 return setError (E_FAIL,
4867 tr ("This machine does not have any snapshots"));
4868 return E_FAIL;
4869 }
4870
4871 aSnapshot = mData->mFirstSnapshot->findChildOrSelf (aName);
4872
4873 if (!aSnapshot)
4874 {
4875 if (aSetError)
4876 return setError (E_FAIL,
4877 tr ("Could not find a snapshot named '%ls'"), aName);
4878 return E_FAIL;
4879 }
4880
4881 return S_OK;
4882}
4883
4884/**
4885 * Searches for an attachment that contains the given hard disk.
4886 * The hard disk must be associated with some VM and can be optionally
4887 * associated with some snapshot. If the attachment is stored in the snapshot
4888 * (i.e. the hard disk is associated with some snapshot), @a aSnapshot
4889 * will point to a non-null object on output.
4890 *
4891 * @param aHd hard disk to search an attachment for
4892 * @param aMachine where to store the hard disk's machine (can be NULL)
4893 * @param aSnapshot where to store the hard disk's snapshot (can be NULL)
4894 * @param aHda where to store the hard disk's attachment (can be NULL)
4895 *
4896 *
4897 * @note
4898 * It is assumed that the machine where the attachment is found,
4899 * is already placed to the Discarding state, when this method is called.
4900 * @note
4901 * The object returned in @a aHda is the attachment from the snapshot
4902 * machine if the hard disk is associated with the snapshot, not from the
4903 * primary machine object returned returned in @a aMachine.
4904 */
4905HRESULT Machine::findHardDiskAttachment (const ComObjPtr <HardDisk> &aHd,
4906 ComObjPtr <Machine> *aMachine,
4907 ComObjPtr <Snapshot> *aSnapshot,
4908 ComObjPtr <HardDiskAttachment> *aHda)
4909{
4910 AssertReturn (!aHd.isNull(), E_INVALIDARG);
4911
4912 Guid mid = aHd->machineId();
4913 Guid sid = aHd->snapshotId();
4914
4915 AssertReturn (!mid.isEmpty(), E_INVALIDARG);
4916
4917 ComObjPtr <Machine> m;
4918 mParent->getMachine (mid, m);
4919 ComAssertRet (!m.isNull(), E_FAIL);
4920
4921 HDData::HDAttachmentList *attachments = &m->mHDData->mHDAttachments;
4922
4923 ComObjPtr <Snapshot> s;
4924 if (!sid.isEmpty())
4925 {
4926 m->findSnapshot (sid, s);
4927 ComAssertRet (!s.isNull(), E_FAIL);
4928 attachments = &s->data().mMachine->mHDData->mHDAttachments;
4929 }
4930
4931 AssertReturn (attachments, E_FAIL);
4932
4933 for (HDData::HDAttachmentList::const_iterator it = attachments->begin();
4934 it != attachments->end();
4935 ++ it)
4936 {
4937 if ((*it)->hardDisk() == aHd)
4938 {
4939 if (aMachine) *aMachine = m;
4940 if (aSnapshot) *aSnapshot = s;
4941 if (aHda) *aHda = (*it);
4942 return S_OK;
4943 }
4944 }
4945
4946 ComAssertFailed();
4947 return E_FAIL;
4948}
4949
4950/**
4951 * Helper for #saveSettings. Cares about renaming the settings directory and
4952 * file if the machine name was changed and about creating a new settings file
4953 * if this is a new machine.
4954 *
4955 * @note Must be never called directly.
4956 *
4957 * @param aRenamed receives |true| if the name was changed and the settings
4958 * file was renamed as a result, or |false| otherwise. The
4959 * value makes sense only on success.
4960 * @param aNew receives |true| if a virgin settings file was created.
4961 */
4962HRESULT Machine::prepareSaveSettings (bool &aRenamed, bool &aNew)
4963{
4964 HRESULT rc = S_OK;
4965
4966 aRenamed = false;
4967
4968 /* if we're ready and isConfigLocked() is FALSE then it means
4969 * that no config file exists yet (we will create a virgin one) */
4970 aNew = !isConfigLocked();
4971
4972 /* attempt to rename the settings file if machine name is changed */
4973 if (mUserData->mNameSync &&
4974 mUserData.isBackedUp() &&
4975 mUserData.backedUpData()->mName != mUserData->mName)
4976 {
4977 aRenamed = true;
4978
4979 if (!aNew)
4980 {
4981 /* unlock the old config file */
4982 rc = unlockConfig();
4983 CheckComRCReturnRC (rc);
4984 }
4985
4986 bool dirRenamed = false;
4987 bool fileRenamed = false;
4988
4989 Utf8Str configFile, newConfigFile;
4990 Utf8Str configDir, newConfigDir;
4991
4992 do
4993 {
4994 int vrc = VINF_SUCCESS;
4995
4996 Utf8Str name = mUserData.backedUpData()->mName;
4997 Utf8Str newName = mUserData->mName;
4998
4999 configFile = mData->mConfigFileFull;
5000
5001 /* first, rename the directory if it matches the machine name */
5002 configDir = configFile;
5003 RTPathStripFilename (configDir.mutableRaw());
5004 newConfigDir = configDir;
5005 if (RTPathFilename (configDir) == name)
5006 {
5007 RTPathStripFilename (newConfigDir.mutableRaw());
5008 newConfigDir = Utf8StrFmt ("%s%c%s",
5009 newConfigDir.raw(), RTPATH_DELIMITER, newName.raw());
5010 /* new dir and old dir cannot be equal here because of 'if'
5011 * above and because name != newName */
5012 Assert (configDir != newConfigDir);
5013 if (!aNew)
5014 {
5015 /* perform real rename only if the machine is not new */
5016 vrc = RTPathRename (configDir.raw(), newConfigDir.raw(), 0);
5017 if (VBOX_FAILURE (vrc))
5018 {
5019 rc = setError (E_FAIL,
5020 tr ("Could not rename the directory '%s' to '%s' "
5021 "to save the settings file (%Vrc)"),
5022 configDir.raw(), newConfigDir.raw(), vrc);
5023 break;
5024 }
5025 dirRenamed = true;
5026 }
5027 }
5028
5029 newConfigFile = Utf8StrFmt ("%s%c%s.xml",
5030 newConfigDir.raw(), RTPATH_DELIMITER, newName.raw());
5031
5032 /* then try to rename the settings file itself */
5033 if (newConfigFile != configFile)
5034 {
5035 /* get the path to old settings file in renamed directory */
5036 configFile = Utf8StrFmt ("%s%c%s",
5037 newConfigDir.raw(), RTPATH_DELIMITER,
5038 RTPathFilename (configFile));
5039 if (!aNew)
5040 {
5041 /* perform real rename only if the machine is not new */
5042 vrc = RTFileRename (configFile.raw(), newConfigFile.raw(), 0);
5043 if (VBOX_FAILURE (vrc))
5044 {
5045 rc = setError (E_FAIL,
5046 tr ("Could not rename the settings file '%s' to '%s' "
5047 "(%Vrc)"),
5048 configFile.raw(), newConfigFile.raw(), vrc);
5049 break;
5050 }
5051 fileRenamed = true;
5052 }
5053 }
5054
5055 /* update mConfigFileFull amd mConfigFile */
5056 Bstr oldConfigFileFull = mData->mConfigFileFull;
5057 Bstr oldConfigFile = mData->mConfigFile;
5058 mData->mConfigFileFull = newConfigFile;
5059 /* try to get the relative path for mConfigFile */
5060 Utf8Str path = newConfigFile;
5061 mParent->calculateRelativePath (path, path);
5062 mData->mConfigFile = path;
5063
5064 /* last, try to update the global settings with the new path */
5065 if (mData->mRegistered)
5066 {
5067 rc = mParent->updateSettings (configDir, newConfigDir);
5068 if (FAILED (rc))
5069 {
5070 /* revert to old values */
5071 mData->mConfigFileFull = oldConfigFileFull;
5072 mData->mConfigFile = oldConfigFile;
5073 break;
5074 }
5075 }
5076
5077 /* update the snapshot folder */
5078 path = mUserData->mSnapshotFolderFull;
5079 if (RTPathStartsWith (path, configDir))
5080 {
5081 path = Utf8StrFmt ("%s%s", newConfigDir.raw(),
5082 path.raw() + configDir.length());
5083 mUserData->mSnapshotFolderFull = path;
5084 calculateRelativePath (path, path);
5085 mUserData->mSnapshotFolder = path;
5086 }
5087
5088 /* update the saved state file path */
5089 path = mSSData->mStateFilePath;
5090 if (RTPathStartsWith (path, configDir))
5091 {
5092 path = Utf8StrFmt ("%s%s", newConfigDir.raw(),
5093 path.raw() + configDir.length());
5094 mSSData->mStateFilePath = path;
5095 }
5096
5097 /* Update saved state file paths of all online snapshots.
5098 * Note that saveSettings() will recognize name change
5099 * and will save all snapshots in this case. */
5100 if (mData->mFirstSnapshot)
5101 mData->mFirstSnapshot->updateSavedStatePaths (configDir,
5102 newConfigDir);
5103 }
5104 while (0);
5105
5106 if (FAILED (rc))
5107 {
5108 /* silently try to rename everything back */
5109 if (fileRenamed)
5110 RTFileRename (newConfigFile.raw(), configFile.raw(), 0);
5111 if (dirRenamed)
5112 RTPathRename (newConfigDir.raw(), configDir.raw(), 0);
5113 }
5114
5115 if (!aNew)
5116 {
5117 /* lock the config again */
5118 HRESULT rc2 = lockConfig();
5119 if (SUCCEEDED (rc))
5120 rc = rc2;
5121 }
5122
5123 CheckComRCReturnRC (rc);
5124 }
5125
5126 if (aNew)
5127 {
5128 /* create a virgin config file */
5129 int vrc = VINF_SUCCESS;
5130
5131 /* ensure the settings directory exists */
5132 Utf8Str path = mData->mConfigFileFull;
5133 RTPathStripFilename (path.mutableRaw());
5134 if (!RTDirExists (path))
5135 {
5136 vrc = RTDirCreateFullPath (path, 0777);
5137 if (VBOX_FAILURE (vrc))
5138 {
5139 return setError (E_FAIL,
5140 tr ("Could not create a directory '%s' "
5141 "to save the settings file (%Vrc)"),
5142 path.raw(), vrc);
5143 }
5144 }
5145
5146 /* Note: open flags must correlated with RTFileOpen() in lockConfig() */
5147 path = Utf8Str (mData->mConfigFileFull);
5148 vrc = RTFileOpen (&mData->mHandleCfgFile, path,
5149 RTFILE_O_READWRITE | RTFILE_O_CREATE |
5150 RTFILE_O_DENY_WRITE);
5151 if (VBOX_SUCCESS (vrc))
5152 {
5153 vrc = RTFileWrite (mData->mHandleCfgFile,
5154 (void *) DefaultMachineConfig,
5155 sizeof (DefaultMachineConfig), NULL);
5156 }
5157 if (VBOX_FAILURE (vrc))
5158 {
5159 mData->mHandleCfgFile = NIL_RTFILE;
5160 return setError (E_FAIL,
5161 tr ("Could not create the settings file '%s' (%Vrc)"),
5162 path.raw(), vrc);
5163 }
5164 /* we do not close the file to simulate lockConfig() */
5165 }
5166
5167 return rc;
5168}
5169
5170/**
5171 * Saves machine data, user data and hardware data.
5172 *
5173 * @param aMarkCurStateAsModified
5174 * if true (default), mData->mCurrentStateModified will be set to
5175 * what #isReallyModified() returns prior to saving settings to a file,
5176 * otherwise the current value of mData->mCurrentStateModified will be
5177 * saved.
5178 * @param aInformCallbacksAnyway
5179 * if true, callbacks will be informed even if #isReallyModified()
5180 * returns false. This is necessary for cases when we change machine data
5181 * diectly, not through the backup()/commit() mechanism.
5182 *
5183 * @note Locks mParent (only in some cases, and only when #isConfigLocked() is
5184 * |TRUE|, see the #prepareSaveSettings() code for details) +
5185 * this object + children for writing.
5186 */
5187HRESULT Machine::saveSettings (bool aMarkCurStateAsModified /* = true */,
5188 bool aInformCallbacksAnyway /* = false */)
5189{
5190 LogFlowThisFuncEnter();
5191
5192 /// @todo (dmik) I guess we should lock all our child objects here
5193 // (such as mVRDPServer etc.) to ensure they are not changed
5194 // until completely saved to disk and committed
5195
5196 /// @todo (dmik) also, we need to delegate saving child objects' settings
5197 // to objects themselves to ensure operations 'commit + save changes'
5198 // are atomic (amd done from the object's lock so that nobody can change
5199 // settings again until completely saved).
5200
5201 AssertReturn (mType == IsMachine || mType == IsSessionMachine, E_FAIL);
5202
5203 bool wasModified;
5204
5205 if (aMarkCurStateAsModified)
5206 {
5207 /*
5208 * We ignore changes to user data when setting mCurrentStateModified
5209 * because the current state will not differ from the current snapshot
5210 * if only user data has been changed (user data is shared by all
5211 * snapshots).
5212 */
5213 mData->mCurrentStateModified = isReallyModified (true /* aIgnoreUserData */);
5214 wasModified = mUserData.hasActualChanges() || mData->mCurrentStateModified;
5215 }
5216 else
5217 {
5218 wasModified = isReallyModified();
5219 }
5220
5221 HRESULT rc = S_OK;
5222
5223 /* First, prepare to save settings. It will will care about renaming the
5224 * settings directory and file if the machine name was changed and about
5225 * creating a new settings file if this is a new machine. */
5226 bool isRenamed = false;
5227 bool isNew = false;
5228 rc = prepareSaveSettings (isRenamed, isNew);
5229 CheckComRCReturnRC (rc);
5230
5231 /* then, open the settings file */
5232 CFGHANDLE configLoader = 0;
5233 rc = openConfigLoader (&configLoader, isNew);
5234 CheckComRCReturnRC (rc);
5235
5236 /* save all snapshots when the machine name was changed since
5237 * it may affect saved state file paths for online snapshots (see
5238 * #openConfigLoader() for details) */
5239 bool updateAllSnapshots = isRenamed;
5240
5241 /* commit before saving, since it may change settings
5242 * (for example, perform fixup of lazy hard disk changes) */
5243 rc = commit();
5244 if (FAILED (rc))
5245 {
5246 closeConfigLoader (configLoader, false /* aSaveBeforeClose */);
5247 return rc;
5248 }
5249
5250 /* include hard disk changes to the modified flag */
5251 wasModified |= mHDData->mHDAttachmentsChanged;
5252 if (aMarkCurStateAsModified)
5253 mData->mCurrentStateModified |= BOOL (mHDData->mHDAttachmentsChanged);
5254
5255
5256 CFGNODE machineNode = 0;
5257 /* create if not exists */
5258 CFGLDRCreateNode (configLoader, "VirtualBox/Machine", &machineNode);
5259
5260 do
5261 {
5262 ComAssertBreak (machineNode, rc = E_FAIL);
5263
5264 /* uuid (required) */
5265 Assert (mData->mUuid);
5266 CFGLDRSetUUID (machineNode, "uuid", mData->mUuid.raw());
5267
5268 /* name (required) */
5269 Assert (!mUserData->mName.isEmpty());
5270 CFGLDRSetBSTR (machineNode, "name", mUserData->mName);
5271
5272 /* nameSync (optional, default is true) */
5273 if (!mUserData->mNameSync)
5274 CFGLDRSetBool (machineNode, "nameSync", false);
5275 else
5276 CFGLDRDeleteAttribute (machineNode, "nameSync");
5277
5278 /* Description node (optional) */
5279 if (!mUserData->mDescription.isNull())
5280 {
5281 CFGNODE descNode = 0;
5282 CFGLDRCreateChildNode (machineNode, "Description", &descNode);
5283 Assert (descNode);
5284 CFGLDRSetBSTR (descNode, NULL, mUserData->mDescription);
5285 CFGLDRReleaseNode (descNode);
5286 }
5287 else
5288 {
5289 CFGNODE descNode = 0;
5290 CFGLDRGetChildNode (machineNode, "Description", 0, &descNode);
5291 if (descNode)
5292 CFGLDRDeleteNode (descNode);
5293 }
5294
5295 /* OSType (required) */
5296 {
5297 CFGLDRSetBSTR (machineNode, "OSType", mUserData->mOSTypeId);
5298 }
5299
5300 /* stateFile (optional) */
5301 if (mData->mMachineState == MachineState_Saved)
5302 {
5303 Assert (!mSSData->mStateFilePath.isEmpty());
5304 /* try to make the file name relative to the settings file dir */
5305 Utf8Str stateFilePath = mSSData->mStateFilePath;
5306 calculateRelativePath (stateFilePath, stateFilePath);
5307 CFGLDRSetString (machineNode, "stateFile", stateFilePath);
5308 }
5309 else
5310 {
5311 Assert (mSSData->mStateFilePath.isNull());
5312 CFGLDRDeleteAttribute (machineNode, "stateFile");
5313 }
5314
5315 /* currentSnapshot ID (optional) */
5316 if (!mData->mCurrentSnapshot.isNull())
5317 {
5318 Assert (!mData->mFirstSnapshot.isNull());
5319 CFGLDRSetUUID (machineNode, "currentSnapshot",
5320 mData->mCurrentSnapshot->data().mId);
5321 }
5322 else
5323 {
5324 Assert (mData->mFirstSnapshot.isNull());
5325 CFGLDRDeleteAttribute (machineNode, "currentSnapshot");
5326 }
5327
5328 /* snapshotFolder (optional) */
5329 if (!mUserData->mSnapshotFolder.isEmpty())
5330 CFGLDRSetBSTR (machineNode, "snapshotFolder", mUserData->mSnapshotFolder);
5331 else
5332 CFGLDRDeleteAttribute (machineNode, "snapshotFolder");
5333
5334 /* currentStateModified (optional, default is yes) */
5335 if (!mData->mCurrentStateModified)
5336 CFGLDRSetBool (machineNode, "currentStateModified", false);
5337 else
5338 CFGLDRDeleteAttribute (machineNode, "currentStateModified");
5339
5340 /* lastStateChange */
5341 CFGLDRSetDateTime (machineNode, "lastStateChange",
5342 mData->mLastStateChange);
5343
5344 /* Hardware node (required) */
5345 {
5346 CFGNODE hwNode = 0;
5347 CFGLDRGetChildNode (machineNode, "Hardware", 0, &hwNode);
5348 /* first, delete the entire node if exists */
5349 if (hwNode)
5350 CFGLDRDeleteNode (hwNode);
5351 /* then recreate it */
5352 hwNode = 0;
5353 CFGLDRCreateChildNode (machineNode, "Hardware", &hwNode);
5354 ComAssertBreak (hwNode, rc = E_FAIL);
5355
5356 rc = saveHardware (hwNode);
5357
5358 CFGLDRReleaseNode (hwNode);
5359 if (FAILED (rc))
5360 break;
5361 }
5362
5363 /* HardDiskAttachments node (required) */
5364 {
5365 CFGNODE hdasNode = 0;
5366 CFGLDRGetChildNode (machineNode, "HardDiskAttachments", 0, &hdasNode);
5367 /* first, delete the entire node if exists */
5368 if (hdasNode)
5369 CFGLDRDeleteNode (hdasNode);
5370 /* then recreate it */
5371 hdasNode = 0;
5372 CFGLDRCreateChildNode (machineNode, "HardDiskAttachments", &hdasNode);
5373 ComAssertBreak (hdasNode, rc = E_FAIL);
5374
5375 rc = saveHardDisks (hdasNode);
5376
5377 CFGLDRReleaseNode (hdasNode);
5378 if (FAILED (rc))
5379 break;
5380 }
5381
5382 /* update all snapshots if requested */
5383 if (updateAllSnapshots)
5384 rc = saveSnapshotSettingsWorker (machineNode, NULL,
5385 SaveSS_UpdateAllOp);
5386 }
5387 while (0);
5388
5389 if (machineNode)
5390 CFGLDRReleaseNode (machineNode);
5391
5392 if (SUCCEEDED (rc))
5393 rc = closeConfigLoader (configLoader, true /* aSaveBeforeClose */);
5394 else
5395 closeConfigLoader (configLoader, false /* aSaveBeforeClose */);
5396
5397 if (FAILED (rc))
5398 {
5399 /*
5400 * backup arbitrary data item to cause #isModified() to still return
5401 * true in case of any error
5402 */
5403 mHWData.backup();
5404 }
5405
5406 if (wasModified || aInformCallbacksAnyway)
5407 {
5408 /*
5409 * Fire the data change event, even on failure (since we've already
5410 * committed all data). This is done only for SessionMachines because
5411 * mutable Machine instances are always not registered (i.e. private
5412 * to the client process that creates them) and thus don't need to
5413 * inform callbacks.
5414 */
5415 if (mType == IsSessionMachine)
5416 mParent->onMachineDataChange (mData->mUuid);
5417 }
5418
5419 LogFlowThisFunc (("rc=%08X\n", rc));
5420 LogFlowThisFuncLeave();
5421 return rc;
5422}
5423
5424/**
5425 * Wrapper for #saveSnapshotSettingsWorker() that opens the settings file
5426 * and locates the <Machine> node in there. See #saveSnapshotSettingsWorker()
5427 * for more details.
5428 *
5429 * @param aSnapshot Snapshot to operate on
5430 * @param aOpFlags Operation to perform, one of SaveSS_NoOp, SaveSS_AddOp
5431 * or SaveSS_UpdateAttrsOp possibly combined with
5432 * SaveSS_UpdateCurrentId.
5433 *
5434 * @note Locks this object for writing + other child objects.
5435 */
5436HRESULT Machine::saveSnapshotSettings (Snapshot *aSnapshot, int aOpFlags)
5437{
5438 AutoCaller autoCaller (this);
5439 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
5440
5441 AssertReturn (mType == IsMachine || mType == IsSessionMachine, E_FAIL);
5442
5443 AutoLock alock (this);
5444
5445 AssertReturn (isConfigLocked(), E_FAIL);
5446
5447 HRESULT rc = S_OK;
5448
5449 /* load the config file */
5450 CFGHANDLE configLoader = 0;
5451 rc = openConfigLoader (&configLoader);
5452 if (FAILED (rc))
5453 return rc;
5454
5455 CFGNODE machineNode = 0;
5456 CFGLDRGetNode (configLoader, "VirtualBox/Machine", 0, &machineNode);
5457
5458 do
5459 {
5460 ComAssertBreak (machineNode, rc = E_FAIL);
5461
5462 rc = saveSnapshotSettingsWorker (machineNode, aSnapshot, aOpFlags);
5463
5464 CFGLDRReleaseNode (machineNode);
5465 }
5466 while (0);
5467
5468 if (SUCCEEDED (rc))
5469 rc = closeConfigLoader (configLoader, true /* aSaveBeforeClose */);
5470 else
5471 closeConfigLoader (configLoader, false /* aSaveBeforeClose */);
5472
5473 return rc;
5474}
5475
5476/**
5477 * Performs the specified operation on the given snapshot
5478 * in the settings file represented by \a aMachineNode.
5479 *
5480 * If \a aOpFlags = SaveSS_UpdateAllOp, \a aSnapshot can be NULL to indicate
5481 * that the whole tree of the snapshots should be updated in <Machine>.
5482 * One particular case is when the last (and the only) snapshot should be
5483 * removed (it is so when both mCurrentSnapshot and mFirstSnapshot are NULL).
5484 *
5485 * \a aOp may be just SaveSS_UpdateCurrentId if only the currentSnapshot
5486 * attribute of <Machine> needs to be updated.
5487 *
5488 * @param aMachineNode <Machine> node in the opened settings file
5489 * @param aSnapshot Snapshot to operate on
5490 * @param aOpFlags Operation to perform, one of SaveSS_NoOp, SaveSS_AddOp
5491 * or SaveSS_UpdateAttrsOp possibly combined with
5492 * SaveSS_UpdateCurrentId.
5493 *
5494 * @note Must be called with this object locked for writing.
5495 * Locks child objects.
5496 */
5497HRESULT Machine::saveSnapshotSettingsWorker (CFGNODE aMachineNode,
5498 Snapshot *aSnapshot, int aOpFlags)
5499{
5500 AssertReturn (aMachineNode, E_FAIL);
5501
5502 AssertReturn (isLockedOnCurrentThread(), E_FAIL);
5503
5504 int op = aOpFlags & SaveSS_OpMask;
5505 AssertReturn (
5506 (aSnapshot && (op == SaveSS_AddOp || op == SaveSS_UpdateAttrsOp ||
5507 op == SaveSS_UpdateAllOp)) ||
5508 (!aSnapshot && ((op == SaveSS_NoOp && (aOpFlags & SaveSS_UpdateCurrentId)) ||
5509 op == SaveSS_UpdateAllOp)),
5510 E_FAIL);
5511
5512 HRESULT rc = S_OK;
5513
5514 bool recreateWholeTree = false;
5515
5516 do
5517 {
5518 if (op == SaveSS_NoOp)
5519 break;
5520
5521 /* quick path: recreate the whole tree of the snapshots */
5522 if (op == SaveSS_UpdateAllOp && !aSnapshot)
5523 {
5524 /* first, delete the entire root snapshot node if it exists */
5525 CFGNODE snapshotNode = 0;
5526 CFGLDRGetChildNode (aMachineNode, "Snapshot", 0, &snapshotNode);
5527 if (snapshotNode)
5528 CFGLDRDeleteNode (snapshotNode);
5529
5530 /*
5531 * second, if we have any snapshots left, substitute aSnapshot with
5532 * the first snapshot to recreate the whole tree, otherwise break
5533 */
5534 if (mData->mFirstSnapshot)
5535 {
5536 aSnapshot = mData->mFirstSnapshot;
5537 recreateWholeTree = true;
5538 }
5539 else
5540 break;
5541 }
5542
5543 Assert (!!aSnapshot);
5544 ComObjPtr <Snapshot> parent = aSnapshot->parent();
5545
5546 if (op == SaveSS_AddOp)
5547 {
5548 CFGNODE parentNode = 0;
5549
5550 if (parent)
5551 {
5552 rc = findSnapshotNode (parent, aMachineNode, NULL, &parentNode);
5553 if (FAILED (rc))
5554 break;
5555 ComAssertBreak (parentNode, rc = E_FAIL);
5556 }
5557
5558 do
5559 {
5560 CFGNODE snapshotsNode = 0;
5561
5562 if (parentNode)
5563 {
5564 CFGLDRCreateChildNode (parentNode, "Snapshots", &snapshotsNode);
5565 ComAssertBreak (snapshotsNode, rc = E_FAIL);
5566 }
5567 else
5568 snapshotsNode = aMachineNode;
5569 do
5570 {
5571 CFGNODE snapshotNode = 0;
5572 CFGLDRAppendChildNode (snapshotsNode, "Snapshot", &snapshotNode);
5573 ComAssertBreak (snapshotNode, rc = E_FAIL);
5574 rc = saveSnapshot (snapshotNode, aSnapshot, false /* aAttrsOnly */);
5575 CFGLDRReleaseNode (snapshotNode);
5576
5577 if (FAILED (rc))
5578 break;
5579
5580 /*
5581 * when a new snapshot is added, this means diffs were created
5582 * for every normal/immutable hard disk of the VM, so we need to
5583 * save the current hard disk attachments
5584 */
5585
5586 CFGNODE hdasNode = 0;
5587 CFGLDRGetChildNode (aMachineNode, "HardDiskAttachments", 0, &hdasNode);
5588 if (hdasNode)
5589 CFGLDRDeleteNode (hdasNode);
5590 CFGLDRCreateChildNode (aMachineNode, "HardDiskAttachments", &hdasNode);
5591 ComAssertBreak (hdasNode, rc = E_FAIL);
5592
5593 rc = saveHardDisks (hdasNode);
5594
5595 if (mHDData->mHDAttachments.size() != 0)
5596 {
5597 /*
5598 * If we have one or more attachments then we definitely
5599 * created diffs for them and associated new diffs with
5600 * current settngs. So, since we don't use saveSettings(),
5601 * we need to inform callbacks manually.
5602 */
5603 if (mType == IsSessionMachine)
5604 mParent->onMachineDataChange (mData->mUuid);
5605 }
5606
5607 CFGLDRReleaseNode (hdasNode);
5608 }
5609 while (0);
5610
5611 if (snapshotsNode != aMachineNode)
5612 CFGLDRReleaseNode (snapshotsNode);
5613 }
5614 while (0);
5615
5616 if (parentNode)
5617 CFGLDRReleaseNode (parentNode);
5618
5619 break;
5620 }
5621
5622 Assert (op == SaveSS_UpdateAttrsOp && !recreateWholeTree ||
5623 op == SaveSS_UpdateAllOp);
5624
5625 CFGNODE snapshotsNode = 0;
5626 CFGNODE snapshotNode = 0;
5627
5628 if (!recreateWholeTree)
5629 {
5630 rc = findSnapshotNode (aSnapshot, aMachineNode,
5631 &snapshotsNode, &snapshotNode);
5632 if (FAILED (rc))
5633 break;
5634 ComAssertBreak (snapshotNode, rc = E_FAIL);
5635 }
5636
5637 if (!snapshotsNode)
5638 snapshotsNode = aMachineNode;
5639
5640 if (op == SaveSS_UpdateAttrsOp)
5641 rc = saveSnapshot (snapshotNode, aSnapshot, true /* aAttrsOnly */);
5642 else do
5643 {
5644 if (snapshotNode)
5645 {
5646 CFGLDRDeleteNode (snapshotNode);
5647 snapshotNode = 0;
5648 }
5649 CFGLDRAppendChildNode (snapshotsNode, "Snapshot", &snapshotNode);
5650 ComAssertBreak (snapshotNode, rc = E_FAIL);
5651 rc = saveSnapshot (snapshotNode, aSnapshot, false /* aAttrsOnly */);
5652 }
5653 while (0);
5654
5655 CFGLDRReleaseNode (snapshotNode);
5656 if (snapshotsNode != aMachineNode)
5657 CFGLDRReleaseNode (snapshotsNode);
5658 }
5659 while (0);
5660
5661 if (SUCCEEDED (rc))
5662 {
5663 /* update currentSnapshot when appropriate */
5664 if (aOpFlags & SaveSS_UpdateCurrentId)
5665 {
5666 if (!mData->mCurrentSnapshot.isNull())
5667 CFGLDRSetUUID (aMachineNode, "currentSnapshot",
5668 mData->mCurrentSnapshot->data().mId);
5669 else
5670 CFGLDRDeleteAttribute (aMachineNode, "currentSnapshot");
5671 }
5672 if (aOpFlags & SaveSS_UpdateCurStateModified)
5673 {
5674 if (!mData->mCurrentStateModified)
5675 CFGLDRSetBool (aMachineNode, "currentStateModified", false);
5676 else
5677 CFGLDRDeleteAttribute (aMachineNode, "currentStateModified");
5678 }
5679 }
5680
5681 return rc;
5682}
5683
5684/**
5685 * Saves the given snapshot and all its children (unless \a aAttrsOnly is true).
5686 * It is assumed that the given node is empty (unless \a aAttrsOnly is true).
5687 *
5688 * @param aNode <Snapshot> node to save the snapshot to
5689 * @param aSnapshot snapshot to save
5690 * @param aAttrsOnly if true, only updatge user-changeable attrs
5691 */
5692HRESULT Machine::saveSnapshot (CFGNODE aNode, Snapshot *aSnapshot, bool aAttrsOnly)
5693{
5694 AssertReturn (aNode && aSnapshot, E_INVALIDARG);
5695 AssertReturn (mType == IsMachine || mType == IsSessionMachine, E_FAIL);
5696
5697 /* uuid (required) */
5698 if (!aAttrsOnly)
5699 CFGLDRSetUUID (aNode, "uuid", aSnapshot->data().mId);
5700
5701 /* name (required) */
5702 CFGLDRSetBSTR (aNode, "name", aSnapshot->data().mName);
5703
5704 /* timeStamp (required) */
5705 CFGLDRSetDateTime (aNode, "timeStamp", aSnapshot->data().mTimeStamp);
5706
5707 /* Description node (optional) */
5708 if (!aSnapshot->data().mDescription.isNull())
5709 {
5710 CFGNODE descNode = 0;
5711 CFGLDRCreateChildNode (aNode, "Description", &descNode);
5712 Assert (descNode);
5713 CFGLDRSetBSTR (descNode, NULL, aSnapshot->data().mDescription);
5714 CFGLDRReleaseNode (descNode);
5715 }
5716 else
5717 {
5718 CFGNODE descNode = 0;
5719 CFGLDRGetChildNode (aNode, "Description", 0, &descNode);
5720 if (descNode)
5721 CFGLDRDeleteNode (descNode);
5722 }
5723
5724 if (aAttrsOnly)
5725 return S_OK;
5726
5727 /* stateFile (optional) */
5728 if (aSnapshot->stateFilePath())
5729 {
5730 /* try to make the file name relative to the settings file dir */
5731 Utf8Str stateFilePath = aSnapshot->stateFilePath();
5732 calculateRelativePath (stateFilePath, stateFilePath);
5733 CFGLDRSetString (aNode, "stateFile", stateFilePath);
5734 }
5735
5736 {
5737 ComObjPtr <SnapshotMachine> snapshotMachine = aSnapshot->data().mMachine;
5738 ComAssertRet (!snapshotMachine.isNull(), E_FAIL);
5739
5740 /* save hardware */
5741 {
5742 CFGNODE hwNode = 0;
5743 CFGLDRCreateChildNode (aNode, "Hardware", &hwNode);
5744
5745 HRESULT rc = snapshotMachine->saveHardware (hwNode);
5746
5747 CFGLDRReleaseNode (hwNode);
5748 if (FAILED (rc))
5749 return rc;
5750 }
5751
5752 /* save hard disks */
5753 {
5754 CFGNODE hdasNode = 0;
5755 CFGLDRCreateChildNode (aNode, "HardDiskAttachments", &hdasNode);
5756
5757 HRESULT rc = snapshotMachine->saveHardDisks (hdasNode);
5758
5759 CFGLDRReleaseNode (hdasNode);
5760 if (FAILED (rc))
5761 return rc;
5762 }
5763 }
5764
5765 /* save children */
5766 {
5767 AutoLock listLock (aSnapshot->childrenLock());
5768
5769 if (aSnapshot->children().size())
5770 {
5771 CFGNODE snapshotsNode = 0;
5772 CFGLDRCreateChildNode (aNode, "Snapshots", &snapshotsNode);
5773
5774 HRESULT rc = S_OK;
5775
5776 for (Snapshot::SnapshotList::const_iterator it = aSnapshot->children().begin();
5777 it != aSnapshot->children().end() && SUCCEEDED (rc);
5778 ++ it)
5779 {
5780 CFGNODE snapshotNode = 0;
5781 CFGLDRCreateChildNode (snapshotsNode, "Snapshot", &snapshotNode);
5782
5783 rc = saveSnapshot (snapshotNode, (*it), aAttrsOnly);
5784
5785 CFGLDRReleaseNode (snapshotNode);
5786 }
5787
5788 CFGLDRReleaseNode (snapshotsNode);
5789 if (FAILED (rc))
5790 return rc;
5791 }
5792 }
5793
5794 return S_OK;
5795}
5796
5797/**
5798 * Creates Saves the VM hardware configuration.
5799 * It is assumed that the given node is empty.
5800 *
5801 * @param aNode <Hardware> node to save the VM hardware confguration to
5802 */
5803HRESULT Machine::saveHardware (CFGNODE aNode)
5804{
5805 AssertReturn (aNode, E_INVALIDARG);
5806
5807 HRESULT rc = S_OK;
5808
5809 /* CPU */
5810 {
5811 CFGNODE cpuNode = 0;
5812 CFGLDRCreateChildNode (aNode, "CPU", &cpuNode);
5813 CFGNODE hwVirtExNode = 0;
5814 CFGLDRCreateChildNode (cpuNode, "HardwareVirtEx", &hwVirtExNode);
5815 const char *value = NULL;
5816 switch (mHWData->mHWVirtExEnabled)
5817 {
5818 case TriStateBool_False:
5819 value = "false";
5820 break;
5821 case TriStateBool_True:
5822 value = "true";
5823 break;
5824 default:
5825 value = "default";
5826 }
5827 CFGLDRSetString (hwVirtExNode, "enabled", value);
5828 CFGLDRReleaseNode (hwVirtExNode);
5829 CFGLDRReleaseNode (cpuNode);
5830 }
5831
5832 /* memory (required) */
5833 {
5834 CFGNODE memoryNode = 0;
5835 CFGLDRCreateChildNode (aNode, "Memory", &memoryNode);
5836 CFGLDRSetUInt32 (memoryNode, "RAMSize", mHWData->mMemorySize);
5837 CFGLDRReleaseNode (memoryNode);
5838 }
5839
5840 /* boot (required) */
5841 do
5842 {
5843 CFGNODE bootNode = 0;
5844 CFGLDRCreateChildNode (aNode, "Boot", &bootNode);
5845
5846 for (ULONG pos = 0; pos < ELEMENTS (mHWData->mBootOrder); pos ++)
5847 {
5848 const char *device = NULL;
5849 switch (mHWData->mBootOrder [pos])
5850 {
5851 case DeviceType_NoDevice:
5852 /* skip, this is allowed for <Order> nodes
5853 * when loading, the default value NoDevice will remain */
5854 continue;
5855 case DeviceType_FloppyDevice: device = "Floppy"; break;
5856 case DeviceType_DVDDevice: device = "DVD"; break;
5857 case DeviceType_HardDiskDevice: device = "HardDisk"; break;
5858 case DeviceType_NetworkDevice: device = "Network"; break;
5859 default:
5860 ComAssertMsgFailedBreak (("Invalid boot device: %d\n",
5861 mHWData->mBootOrder [pos]),
5862 rc = E_FAIL);
5863 }
5864 if (FAILED (rc))
5865 break;
5866
5867 CFGNODE orderNode = 0;
5868 CFGLDRAppendChildNode (bootNode, "Order", &orderNode);
5869
5870 CFGLDRSetUInt32 (orderNode, "position", pos + 1);
5871 CFGLDRSetString (orderNode, "device", device);
5872
5873 CFGLDRReleaseNode (orderNode);
5874 }
5875
5876 CFGLDRReleaseNode (bootNode);
5877 }
5878 while (0);
5879
5880 CheckComRCReturnRC (rc);
5881
5882 /* display (required) */
5883 {
5884 CFGNODE displayNode = 0;
5885 CFGLDRCreateChildNode (aNode, "Display", &displayNode);
5886 CFGLDRSetUInt32 (displayNode, "VRAMSize", mHWData->mVRAMSize);
5887 CFGLDRSetUInt32 (displayNode, "MonitorCount", mHWData->mMonitorCount);
5888 CFGLDRReleaseNode (displayNode);
5889 }
5890
5891#ifdef VBOX_VRDP
5892 /* VRDP settings (optional) */
5893 {
5894 CFGNODE remoteDisplayNode = 0;
5895 CFGLDRCreateChildNode (aNode, "RemoteDisplay", &remoteDisplayNode);
5896 if (remoteDisplayNode)
5897 {
5898 rc = mVRDPServer->saveSettings (remoteDisplayNode);
5899 CFGLDRReleaseNode (remoteDisplayNode);
5900 CheckComRCReturnRC (rc);
5901 }
5902 }
5903#endif
5904
5905 /* BIOS (required) */
5906 {
5907 CFGNODE biosNode = 0;
5908 CFGLDRCreateChildNode (aNode, "BIOS", &biosNode);
5909 {
5910 BOOL fSet;
5911 /* ACPI */
5912 CFGNODE acpiNode = 0;
5913 CFGLDRCreateChildNode (biosNode, "ACPI", &acpiNode);
5914 mBIOSSettings->COMGETTER(ACPIEnabled)(&fSet);
5915 CFGLDRSetBool (acpiNode, "enabled", !!fSet);
5916 CFGLDRReleaseNode (acpiNode);
5917
5918 /* IOAPIC */
5919 CFGNODE ioapicNode = 0;
5920 CFGLDRCreateChildNode (biosNode, "IOAPIC", &ioapicNode);
5921 mBIOSSettings->COMGETTER(IOAPICEnabled)(&fSet);
5922 CFGLDRSetBool (ioapicNode, "enabled", !!fSet);
5923 CFGLDRReleaseNode (ioapicNode);
5924
5925 /* BIOS logo (optional) **/
5926 CFGNODE logoNode = 0;
5927 CFGLDRCreateChildNode (biosNode, "Logo", &logoNode);
5928 mBIOSSettings->COMGETTER(LogoFadeIn)(&fSet);
5929 CFGLDRSetBool (logoNode, "fadeIn", !!fSet);
5930 mBIOSSettings->COMGETTER(LogoFadeOut)(&fSet);
5931 CFGLDRSetBool (logoNode, "fadeOut", !!fSet);
5932 ULONG ulDisplayTime;
5933 mBIOSSettings->COMGETTER(LogoDisplayTime)(&ulDisplayTime);
5934 CFGLDRSetUInt32 (logoNode, "displayTime", ulDisplayTime);
5935 Bstr logoPath;
5936 mBIOSSettings->COMGETTER(LogoImagePath)(logoPath.asOutParam());
5937 if (logoPath)
5938 CFGLDRSetBSTR (logoNode, "imagePath", logoPath);
5939 else
5940 CFGLDRDeleteAttribute (logoNode, "imagePath");
5941 CFGLDRReleaseNode (logoNode);
5942
5943 /* boot menu (optional) */
5944 CFGNODE bootMenuNode = 0;
5945 CFGLDRCreateChildNode (biosNode, "BootMenu", &bootMenuNode);
5946 BIOSBootMenuMode_T bootMenuMode;
5947 Bstr bootMenuModeStr;
5948 mBIOSSettings->COMGETTER(BootMenuMode)(&bootMenuMode);
5949 switch (bootMenuMode)
5950 {
5951 case BIOSBootMenuMode_Disabled:
5952 bootMenuModeStr = "disabled";
5953 break;
5954 case BIOSBootMenuMode_MenuOnly:
5955 bootMenuModeStr = "menuonly";
5956 break;
5957 default:
5958 bootMenuModeStr = "messageandmenu";
5959 }
5960 CFGLDRSetBSTR (bootMenuNode, "mode", bootMenuModeStr);
5961 CFGLDRReleaseNode (bootMenuNode);
5962
5963 /* time offset (optional) */
5964 CFGNODE timeOffsetNode = 0;
5965 CFGLDRCreateChildNode (biosNode, "TimeOffset", &timeOffsetNode);
5966 LONG64 timeOffset;
5967 mBIOSSettings->COMGETTER(TimeOffset)(&timeOffset);
5968 CFGLDRSetInt64 (timeOffsetNode, "value", timeOffset);
5969 CFGLDRReleaseNode (timeOffsetNode);
5970 }
5971 CFGLDRReleaseNode(biosNode);
5972 }
5973
5974 /* DVD drive (required) */
5975 /// @todo (dmik) move the code to DVDDrive
5976 do
5977 {
5978 CFGNODE dvdNode = 0;
5979 CFGLDRCreateChildNode (aNode, "DVDDrive", &dvdNode);
5980
5981 BOOL fPassthrough;
5982 mDVDDrive->COMGETTER(Passthrough)(&fPassthrough);
5983 CFGLDRSetBool(dvdNode, "passthrough", !!fPassthrough);
5984
5985 switch (mDVDDrive->data()->mDriveState)
5986 {
5987 case DriveState_ImageMounted:
5988 {
5989 Assert (!mDVDDrive->data()->mDVDImage.isNull());
5990
5991 Guid id;
5992 rc = mDVDDrive->data()->mDVDImage->COMGETTER(Id) (id.asOutParam());
5993 Assert (!id.isEmpty());
5994
5995 CFGNODE imageNode = 0;
5996 CFGLDRCreateChildNode (dvdNode, "Image", &imageNode);
5997 CFGLDRSetUUID (imageNode, "uuid", id.ptr());
5998 CFGLDRReleaseNode (imageNode);
5999 break;
6000 }
6001 case DriveState_HostDriveCaptured:
6002 {
6003 Assert (!mDVDDrive->data()->mHostDrive.isNull());
6004
6005 Bstr name;
6006 rc = mDVDDrive->data()->mHostDrive->COMGETTER(Name) (name.asOutParam());
6007 Assert (!name.isEmpty());
6008
6009 CFGNODE hostDriveNode = 0;
6010 CFGLDRCreateChildNode (dvdNode, "HostDrive", &hostDriveNode);
6011 CFGLDRSetBSTR (hostDriveNode, "src", name);
6012 CFGLDRReleaseNode (hostDriveNode);
6013 break;
6014 }
6015 case DriveState_NotMounted:
6016 /* do nothing, i.e.leave the DVD drive node empty */
6017 break;
6018 default:
6019 ComAssertMsgFailedBreak (("Invalid DVD drive state: %d\n",
6020 mDVDDrive->data()->mDriveState),
6021 rc = E_FAIL);
6022 }
6023
6024 CFGLDRReleaseNode (dvdNode);
6025 }
6026 while (0);
6027
6028 CheckComRCReturnRC (rc);
6029
6030 /* Flooppy drive (required) */
6031 /// @todo (dmik) move the code to DVDDrive
6032 do
6033 {
6034 CFGNODE floppyNode = 0;
6035 CFGLDRCreateChildNode (aNode, "FloppyDrive", &floppyNode);
6036
6037 BOOL fFloppyEnabled;
6038 rc = mFloppyDrive->COMGETTER(Enabled)(&fFloppyEnabled);
6039 CFGLDRSetBool (floppyNode, "enabled", !!fFloppyEnabled);
6040
6041 switch (mFloppyDrive->data()->mDriveState)
6042 {
6043 case DriveState_ImageMounted:
6044 {
6045 Assert (!mFloppyDrive->data()->mFloppyImage.isNull());
6046
6047 Guid id;
6048 rc = mFloppyDrive->data()->mFloppyImage->COMGETTER(Id) (id.asOutParam());
6049 Assert (!id.isEmpty());
6050
6051 CFGNODE imageNode = 0;
6052 CFGLDRCreateChildNode (floppyNode, "Image", &imageNode);
6053 CFGLDRSetUUID (imageNode, "uuid", id.ptr());
6054 CFGLDRReleaseNode (imageNode);
6055 break;
6056 }
6057 case DriveState_HostDriveCaptured:
6058 {
6059 Assert (!mFloppyDrive->data()->mHostDrive.isNull());
6060
6061 Bstr name;
6062 rc = mFloppyDrive->data()->mHostDrive->COMGETTER(Name) (name.asOutParam());
6063 Assert (!name.isEmpty());
6064
6065 CFGNODE hostDriveNode = 0;
6066 CFGLDRCreateChildNode (floppyNode, "HostDrive", &hostDriveNode);
6067 CFGLDRSetBSTR (hostDriveNode, "src", name);
6068 CFGLDRReleaseNode (hostDriveNode);
6069 break;
6070 }
6071 case DriveState_NotMounted:
6072 /* do nothing, i.e.leave the Floppy drive node empty */
6073 break;
6074 default:
6075 ComAssertMsgFailedBreak (("Invalid Floppy drive state: %d\n",
6076 mFloppyDrive->data()->mDriveState),
6077 rc = E_FAIL);
6078 }
6079
6080 CFGLDRReleaseNode (floppyNode);
6081 }
6082 while (0);
6083
6084 CheckComRCReturnRC (rc);
6085
6086
6087 /* USB Controller (required) */
6088 rc = mUSBController->saveSettings (aNode);
6089 CheckComRCReturnRC (rc);
6090
6091 /* Network adapters (required) */
6092 do
6093 {
6094 CFGNODE nwNode = 0;
6095 CFGLDRCreateChildNode (aNode, "Network", &nwNode);
6096
6097 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
6098 {
6099 CFGNODE networkAdapterNode = 0;
6100 CFGLDRAppendChildNode (nwNode, "Adapter", &networkAdapterNode);
6101
6102 CFGLDRSetUInt32 (networkAdapterNode, "slot", slot);
6103 CFGLDRSetBool (networkAdapterNode, "enabled",
6104 !!mNetworkAdapters [slot]->data()->mEnabled);
6105 CFGLDRSetBSTR (networkAdapterNode, "MACAddress",
6106 mNetworkAdapters [slot]->data()->mMACAddress);
6107 CFGLDRSetBool (networkAdapterNode, "cable",
6108 !!mNetworkAdapters [slot]->data()->mCableConnected);
6109
6110 if (mNetworkAdapters [slot]->data()->mTraceEnabled)
6111 CFGLDRSetBool (networkAdapterNode, "trace", true);
6112
6113 CFGLDRSetBSTR (networkAdapterNode, "tracefile",
6114 mNetworkAdapters [slot]->data()->mTraceFile);
6115
6116 switch (mNetworkAdapters [slot]->data()->mAdapterType)
6117 {
6118 case NetworkAdapterType_NetworkAdapterAm79C970A:
6119 CFGLDRSetString (networkAdapterNode, "type", "Am79C970A");
6120 break;
6121 case NetworkAdapterType_NetworkAdapterAm79C973:
6122 CFGLDRSetString (networkAdapterNode, "type", "Am79C973");
6123 break;
6124 default:
6125 ComAssertMsgFailedBreak (("Invalid network adapter type: %d\n",
6126 mNetworkAdapters [slot]->data()->mAdapterType),
6127 rc = E_FAIL);
6128 }
6129
6130 CFGNODE attachmentNode = 0;
6131 switch (mNetworkAdapters [slot]->data()->mAttachmentType)
6132 {
6133 case NetworkAttachmentType_NoNetworkAttachment:
6134 {
6135 /* do nothing -- empty content */
6136 break;
6137 }
6138 case NetworkAttachmentType_NATNetworkAttachment:
6139 {
6140 CFGLDRAppendChildNode (networkAdapterNode, "NAT", &attachmentNode);
6141 break;
6142 }
6143 case NetworkAttachmentType_HostInterfaceNetworkAttachment:
6144 {
6145 CFGLDRAppendChildNode (networkAdapterNode, "HostInterface", &attachmentNode);
6146 const Bstr &name = mNetworkAdapters [slot]->data()->mHostInterface;
6147#ifdef RT_OS_WINDOWS
6148 Assert (!name.isNull());
6149#endif
6150#ifdef VBOX_WITH_UNIXY_TAP_NETWORKING
6151 if (!name.isEmpty())
6152#endif
6153 CFGLDRSetBSTR (attachmentNode, "name", name);
6154#ifdef VBOX_WITH_UNIXY_TAP_NETWORKING
6155 const Bstr &tapSetupApp =
6156 mNetworkAdapters [slot]->data()->mTAPSetupApplication;
6157 if (!tapSetupApp.isEmpty())
6158 CFGLDRSetBSTR (attachmentNode, "TAPSetup", tapSetupApp);
6159 const Bstr &tapTerminateApp =
6160 mNetworkAdapters [slot]->data()->mTAPTerminateApplication;
6161 if (!tapTerminateApp.isEmpty())
6162 CFGLDRSetBSTR (attachmentNode, "TAPTerminate", tapTerminateApp);
6163#endif /* VBOX_WITH_UNIXY_TAP_NETWORKING */
6164 break;
6165 }
6166 case NetworkAttachmentType_InternalNetworkAttachment:
6167 {
6168 CFGLDRAppendChildNode (networkAdapterNode, "InternalNetwork", &attachmentNode);
6169 const Bstr &name = mNetworkAdapters[slot]->data()->mInternalNetwork;
6170 Assert(!name.isNull());
6171 CFGLDRSetBSTR (attachmentNode, "name", name);
6172 break;
6173 }
6174 default:
6175 {
6176 ComAssertFailedBreak (rc = E_FAIL);
6177 break;
6178 }
6179 }
6180 if (attachmentNode)
6181 CFGLDRReleaseNode (attachmentNode);
6182
6183 CFGLDRReleaseNode (networkAdapterNode);
6184 }
6185
6186 CFGLDRReleaseNode (nwNode);
6187 }
6188 while (0);
6189
6190 if (FAILED (rc))
6191 return rc;
6192
6193 /* Serial ports */
6194 CFGNODE serialNode = 0;
6195 CFGLDRCreateChildNode (aNode, "Uart", &serialNode);
6196
6197 for (ULONG slot = 0; slot < ELEMENTS (mSerialPorts); slot++)
6198 {
6199 rc = mSerialPorts [slot]->saveSettings (serialNode);
6200 CheckComRCReturnRC (rc);
6201 }
6202 CFGLDRReleaseNode (serialNode);
6203
6204 /* Parallel ports */
6205 CFGNODE parallelNode = 0;
6206 CFGLDRCreateChildNode (aNode, "Lpt", &parallelNode);
6207
6208 for (ULONG slot = 0; slot < ELEMENTS (mParallelPorts); slot++)
6209 {
6210 rc = mParallelPorts [slot]->saveSettings (parallelNode);
6211 CheckComRCReturnRC (rc);
6212 }
6213 CFGLDRReleaseNode (parallelNode);
6214
6215 /* Audio adapter */
6216 do
6217 {
6218 CFGNODE adapterNode = 0;
6219 CFGLDRCreateChildNode (aNode, "AudioAdapter", &adapterNode);
6220
6221 switch (mAudioAdapter->data()->mAudioDriver)
6222 {
6223 case AudioDriverType_NullAudioDriver:
6224 {
6225 CFGLDRSetString (adapterNode, "driver", "null");
6226 break;
6227 }
6228#ifdef RT_OS_WINDOWS
6229 case AudioDriverType_WINMMAudioDriver:
6230# ifdef VBOX_WITH_WINMM
6231 {
6232 CFGLDRSetString (adapterNode, "driver", "winmm");
6233 break;
6234 }
6235# endif
6236 case AudioDriverType_DSOUNDAudioDriver:
6237 {
6238 CFGLDRSetString (adapterNode, "driver", "dsound");
6239 break;
6240 }
6241#endif /* RT_OS_WINDOWS */
6242#ifdef RT_OS_LINUX
6243 case AudioDriverType_ALSAAudioDriver:
6244# ifdef VBOX_WITH_ALSA
6245 {
6246 CFGLDRSetString (adapterNode, "driver", "alsa");
6247 break;
6248 }
6249# endif
6250 case AudioDriverType_OSSAudioDriver:
6251 {
6252 CFGLDRSetString (adapterNode, "driver", "oss");
6253 break;
6254 }
6255#endif /* RT_OS_LINUX */
6256#ifdef RT_OS_DARWIN
6257 case AudioDriverType_CoreAudioDriver:
6258 {
6259 CFGLDRSetString (adapterNode, "driver", "coreaudio");
6260 break;
6261 }
6262#endif
6263#ifdef RT_OS_OS2
6264 case AudioDriverType_MMPMAudioDriver:
6265 {
6266 CFGLDRSetString (adapterNode, "driver", "mmpm");
6267 break;
6268 }
6269#endif
6270 default:
6271 ComAssertMsgFailedBreak (("Wrong audio driver type! driver = %d\n",
6272 mAudioAdapter->data()->mAudioDriver),
6273 rc = E_FAIL);
6274 }
6275
6276 CFGLDRSetBool (adapterNode, "enabled", !!mAudioAdapter->data()->mEnabled);
6277
6278 CFGLDRReleaseNode (adapterNode);
6279 }
6280 while (0);
6281
6282 if (FAILED (rc))
6283 return rc;
6284
6285 /* Shared folders */
6286 do
6287 {
6288 CFGNODE sharedFoldersNode = 0;
6289 CFGLDRCreateChildNode (aNode, "SharedFolders", &sharedFoldersNode);
6290
6291 for (HWData::SharedFolderList::const_iterator it = mHWData->mSharedFolders.begin();
6292 it != mHWData->mSharedFolders.end();
6293 ++ it)
6294 {
6295 ComObjPtr <SharedFolder> folder = *it;
6296
6297 CFGNODE folderNode = 0;
6298 CFGLDRAppendChildNode (sharedFoldersNode, "SharedFolder", &folderNode);
6299
6300 /* all are mandatory */
6301 CFGLDRSetBSTR (folderNode, "name", folder->name());
6302 CFGLDRSetBSTR (folderNode, "hostPath", folder->hostPath());
6303
6304 CFGLDRReleaseNode (folderNode);
6305 }
6306
6307 CFGLDRReleaseNode (sharedFoldersNode);
6308 }
6309 while (0);
6310
6311 /* Clipboard */
6312 {
6313 CFGNODE clipNode = 0;
6314 CFGLDRCreateChildNode (aNode, "Clipboard", &clipNode);
6315
6316 const char *mode = "Disabled";
6317 switch (mHWData->mClipboardMode)
6318 {
6319 case ClipboardMode_ClipDisabled:
6320 /* already assigned */
6321 break;
6322 case ClipboardMode_ClipHostToGuest:
6323 mode = "HostToGuest";
6324 break;
6325 case ClipboardMode_ClipGuestToHost:
6326 mode = "GuestToHost";
6327 break;
6328 case ClipboardMode_ClipBidirectional:
6329 mode = "Bidirectional";
6330 break;
6331 default:
6332 AssertMsgFailed (("Clipboard mode %d is invalid",
6333 mHWData->mClipboardMode));
6334 break;
6335 }
6336 CFGLDRSetString (clipNode, "mode", mode);
6337
6338 CFGLDRReleaseNode (clipNode);
6339 }
6340
6341 return rc;
6342}
6343
6344/**
6345 * Saves the hard disk confguration.
6346 * It is assumed that the given node is empty.
6347 *
6348 * @param aNode <HardDiskAttachments> node to save the hard disk confguration to
6349 */
6350HRESULT Machine::saveHardDisks (CFGNODE aNode)
6351{
6352 AssertReturn (aNode, E_INVALIDARG);
6353
6354 HRESULT rc = S_OK;
6355
6356 for (HDData::HDAttachmentList::const_iterator it = mHDData->mHDAttachments.begin();
6357 it != mHDData->mHDAttachments.end() && SUCCEEDED (rc);
6358 ++ it)
6359 {
6360 ComObjPtr <HardDiskAttachment> att = *it;
6361
6362 CFGNODE hdNode = 0;
6363 CFGLDRAppendChildNode (aNode, "HardDiskAttachment", &hdNode);
6364
6365 do
6366 {
6367 const char *bus = NULL;
6368 switch (att->controller())
6369 {
6370 case DiskControllerType_IDE0Controller: bus = "ide0"; break;
6371 case DiskControllerType_IDE1Controller: bus = "ide1"; break;
6372 default:
6373 ComAssertFailedBreak (rc = E_FAIL);
6374 }
6375 if (FAILED (rc))
6376 break;
6377
6378 const char *dev = NULL;
6379 switch (att->deviceNumber())
6380 {
6381 case 0: dev = "master"; break;
6382 case 1: dev = "slave"; break;
6383 default:
6384 ComAssertFailedBreak (rc = E_FAIL);
6385 }
6386 if (FAILED (rc))
6387 break;
6388
6389 CFGLDRSetUUID (hdNode, "hardDisk", att->hardDisk()->id());
6390 CFGLDRSetString (hdNode, "bus", bus);
6391 CFGLDRSetString (hdNode, "device", dev);
6392 }
6393 while (0);
6394
6395 CFGLDRReleaseNode (hdNode);
6396 }
6397
6398 return rc;
6399}
6400
6401/**
6402 * Saves machine state settings as defined by aFlags
6403 * (SaveSTS_* values).
6404 *
6405 * @param aFlags a combination of SaveSTS_* flags
6406 *
6407 * @note Locks objects!
6408 */
6409HRESULT Machine::saveStateSettings (int aFlags)
6410{
6411 if (aFlags == 0)
6412 return S_OK;
6413
6414 AutoCaller autoCaller (this);
6415 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
6416
6417 AutoLock alock (this);
6418
6419 /* load the config file */
6420 CFGHANDLE configLoader = 0;
6421 HRESULT rc = openConfigLoader (&configLoader);
6422 if (FAILED (rc))
6423 return rc;
6424
6425 CFGNODE machineNode = 0;
6426 CFGLDRGetNode (configLoader, "VirtualBox/Machine", 0, &machineNode);
6427
6428 do
6429 {
6430 ComAssertBreak (machineNode, rc = E_FAIL);
6431
6432 if (aFlags & SaveSTS_CurStateModified)
6433 {
6434 if (!mData->mCurrentStateModified)
6435 CFGLDRSetBool (machineNode, "currentStateModified", false);
6436 else
6437 CFGLDRDeleteAttribute (machineNode, "currentStateModified");
6438 }
6439
6440 if (aFlags & SaveSTS_StateFilePath)
6441 {
6442 if (mSSData->mStateFilePath)
6443 CFGLDRSetBSTR (machineNode, "stateFile", mSSData->mStateFilePath);
6444 else
6445 CFGLDRDeleteAttribute (machineNode, "stateFile");
6446 }
6447
6448 if (aFlags & SaveSTS_StateTimeStamp)
6449 {
6450 Assert (mData->mMachineState != MachineState_Aborted ||
6451 mSSData->mStateFilePath.isNull());
6452
6453 CFGLDRSetDateTime (machineNode, "lastStateChange",
6454 mData->mLastStateChange);
6455
6456 // set the aborted attribute when appropriate
6457 if (mData->mMachineState == MachineState_Aborted)
6458 CFGLDRSetBool (machineNode, "aborted", true);
6459 else
6460 CFGLDRDeleteAttribute (machineNode, "aborted");
6461 }
6462 }
6463 while (0);
6464
6465 if (machineNode)
6466 CFGLDRReleaseNode (machineNode);
6467
6468 if (SUCCEEDED (rc))
6469 rc = closeConfigLoader (configLoader, true /* aSaveBeforeClose */);
6470 else
6471 closeConfigLoader (configLoader, false /* aSaveBeforeClose */);
6472
6473 return rc;
6474}
6475
6476/**
6477 * Cleans up all differencing hard disks based on immutable hard disks.
6478 *
6479 * @note Locks objects!
6480 */
6481HRESULT Machine::wipeOutImmutableDiffs()
6482{
6483 AutoCaller autoCaller (this);
6484 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
6485
6486 AutoReaderLock alock (this);
6487
6488 AssertReturn (mData->mMachineState == MachineState_PoweredOff ||
6489 mData->mMachineState == MachineState_Aborted, E_FAIL);
6490
6491 for (HDData::HDAttachmentList::const_iterator it = mHDData->mHDAttachments.begin();
6492 it != mHDData->mHDAttachments.end();
6493 ++ it)
6494 {
6495 ComObjPtr <HardDisk> hd = (*it)->hardDisk();
6496 AutoLock hdLock (hd);
6497
6498 if(hd->isParentImmutable())
6499 {
6500 /// @todo (dmik) no error handling for now
6501 // (need async error reporting for this)
6502 hd->asVDI()->wipeOutImage();
6503 }
6504 }
6505
6506 return S_OK;
6507}
6508
6509/**
6510 * Fixes up lazy hard disk attachments by creating or deleting differencing
6511 * hard disks when machine settings are being committed.
6512 * Must be called only from #commit().
6513 *
6514 * @note Locks objects!
6515 */
6516HRESULT Machine::fixupHardDisks (bool aCommit)
6517{
6518 AutoCaller autoCaller (this);
6519 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
6520
6521 AutoLock alock (this);
6522
6523 /* no attac/detach operations -- nothing to do */
6524 if (!mHDData.isBackedUp())
6525 {
6526 mHDData->mHDAttachmentsChanged = false;
6527 return S_OK;
6528 }
6529
6530 AssertReturn (mData->mRegistered, E_FAIL);
6531
6532 if (aCommit)
6533 {
6534 /*
6535 * changes are being committed,
6536 * perform actual diff image creation, deletion etc.
6537 */
6538
6539 /* take a copy of backed up attachments (will modify it) */
6540 HDData::HDAttachmentList backedUp = mHDData.backedUpData()->mHDAttachments;
6541 /* list of new diffs created */
6542 std::list <ComObjPtr <HardDisk> > newDiffs;
6543
6544 HRESULT rc = S_OK;
6545
6546 /* go through current attachments */
6547 for (HDData::HDAttachmentList::const_iterator
6548 it = mHDData->mHDAttachments.begin();
6549 it != mHDData->mHDAttachments.end();
6550 ++ it)
6551 {
6552 ComObjPtr <HardDiskAttachment> hda = *it;
6553 ComObjPtr <HardDisk> hd = hda->hardDisk();
6554 AutoLock hdLock (hd);
6555
6556 if (!hda->isDirty())
6557 {
6558 /*
6559 * not dirty, therefore was either attached before backing up
6560 * or doesn't need any fixup (already fixed up); try to locate
6561 * this hard disk among backed up attachments and remove from
6562 * there to prevent it from being deassociated/deleted
6563 */
6564 HDData::HDAttachmentList::iterator oldIt;
6565 for (oldIt = backedUp.begin(); oldIt != backedUp.end(); ++ oldIt)
6566 if ((*oldIt)->hardDisk().equalsTo (hd))
6567 break;
6568 if (oldIt != backedUp.end())
6569 {
6570 /* remove from there */
6571 backedUp.erase (oldIt);
6572 Log3 (("FC: %ls found in old\n", hd->toString().raw()));
6573 }
6574 }
6575 else
6576 {
6577 /* dirty, determine what to do */
6578
6579 bool needDiff = false;
6580 bool searchAmongSnapshots = false;
6581
6582 switch (hd->type())
6583 {
6584 case HardDiskType_ImmutableHardDisk:
6585 {
6586 /* decrease readers increased in AttachHardDisk() */
6587 hd->releaseReader();
6588 Log3 (("FC: %ls released\n", hd->toString().raw()));
6589 /* indicate we need a diff (indirect attachment) */
6590 needDiff = true;
6591 break;
6592 }
6593 case HardDiskType_WritethroughHardDisk:
6594 {
6595 /* reset the dirty flag */
6596 hda->updateHardDisk (hd, false /* aDirty */);
6597 Log3 (("FC: %ls updated\n", hd->toString().raw()));
6598 break;
6599 }
6600 case HardDiskType_NormalHardDisk:
6601 {
6602 if (hd->snapshotId().isEmpty())
6603 {
6604 /* reset the dirty flag */
6605 hda->updateHardDisk (hd, false /* aDirty */);
6606 Log3 (("FC: %ls updated\n", hd->toString().raw()));
6607 }
6608 else
6609 {
6610 /* decrease readers increased in AttachHardDisk() */
6611 hd->releaseReader();
6612 Log3 (("FC: %ls released\n", hd->toString().raw()));
6613 /* indicate we need a diff (indirect attachment) */
6614 needDiff = true;
6615 /* search for the most recent base among snapshots */
6616 searchAmongSnapshots = true;
6617 }
6618 break;
6619 }
6620 }
6621
6622 if (!needDiff)
6623 continue;
6624
6625 bool createDiff = false;
6626
6627 /*
6628 * see whether any previously attached hard disk has the
6629 * the currently attached one (Normal or Independent) as
6630 * the root
6631 */
6632
6633 HDData::HDAttachmentList::iterator foundIt = backedUp.end();
6634
6635 for (HDData::HDAttachmentList::iterator it = backedUp.begin();
6636 it != backedUp.end();
6637 ++ it)
6638 {
6639 if ((*it)->hardDisk()->root().equalsTo (hd))
6640 {
6641 /*
6642 * matched dev and ctl (i.e. attached to the same place)
6643 * will win and immediately stop the search; otherwise
6644 * the first attachment that matched the hd only will
6645 * be used
6646 */
6647 if ((*it)->deviceNumber() == hda->deviceNumber() &&
6648 (*it)->controller() == hda->controller())
6649 {
6650 foundIt = it;
6651 break;
6652 }
6653 else
6654 if (foundIt == backedUp.end())
6655 {
6656 /*
6657 * not an exact match; ensure there is no exact match
6658 * among other current attachments referring the same
6659 * root (to prevent this attachmend from reusing the
6660 * hard disk of the other attachment that will later
6661 * give the exact match or already gave it before)
6662 */
6663 bool canReuse = true;
6664 for (HDData::HDAttachmentList::const_iterator
6665 it2 = mHDData->mHDAttachments.begin();
6666 it2 != mHDData->mHDAttachments.end();
6667 ++ it2)
6668 {
6669 if ((*it2)->deviceNumber() == (*it)->deviceNumber() &&
6670 (*it2)->controller() == (*it)->controller() &&
6671 (*it2)->hardDisk()->root().equalsTo (hd))
6672 {
6673 /*
6674 * the exact match, either non-dirty or dirty
6675 * one refers the same root: in both cases
6676 * we cannot reuse the hard disk, so break
6677 */
6678 canReuse = false;
6679 break;
6680 }
6681 }
6682
6683 if (canReuse)
6684 foundIt = it;
6685 }
6686 }
6687 }
6688
6689 if (foundIt != backedUp.end())
6690 {
6691 /* found either one or another, reuse the diff */
6692 hda->updateHardDisk ((*foundIt)->hardDisk(),
6693 false /* aDirty */);
6694 Log3 (("FC: %ls reused as %ls\n", hd->toString().raw(),
6695 (*foundIt)->hardDisk()->toString().raw()));
6696 /* remove from there */
6697 backedUp.erase (foundIt);
6698 }
6699 else
6700 {
6701 /* was not attached, need a diff */
6702 createDiff = true;
6703 }
6704
6705 if (!createDiff)
6706 continue;
6707
6708 ComObjPtr <HardDisk> baseHd = hd;
6709
6710 if (searchAmongSnapshots)
6711 {
6712 /*
6713 * find the most recent diff based on the currently
6714 * attached root (Normal hard disk) among snapshots
6715 */
6716
6717 ComObjPtr <Snapshot> snap = mData->mCurrentSnapshot;
6718
6719 while (snap)
6720 {
6721 AutoLock snapLock (snap);
6722
6723 const HDData::HDAttachmentList &snapAtts =
6724 snap->data().mMachine->hdData()->mHDAttachments;
6725
6726 HDData::HDAttachmentList::const_iterator foundIt = snapAtts.end();
6727
6728 for (HDData::HDAttachmentList::const_iterator
6729 it = snapAtts.begin(); it != snapAtts.end(); ++ it)
6730 {
6731 if ((*it)->hardDisk()->root().equalsTo (hd))
6732 {
6733 /*
6734 * matched dev and ctl (i.e. attached to the same place)
6735 * will win and immediately stop the search; otherwise
6736 * the first attachment that matched the hd only will
6737 * be used
6738 */
6739 if ((*it)->deviceNumber() == hda->deviceNumber() &&
6740 (*it)->controller() == hda->controller())
6741 {
6742 foundIt = it;
6743 break;
6744 }
6745 else
6746 if (foundIt == snapAtts.end())
6747 foundIt = it;
6748 }
6749 }
6750
6751 if (foundIt != snapAtts.end())
6752 {
6753 /* the most recent diff has been found, use as a base */
6754 baseHd = (*foundIt)->hardDisk();
6755 Log3 (("FC: %ls: recent found %ls\n",
6756 hd->toString().raw(), baseHd->toString().raw()));
6757 break;
6758 }
6759
6760 snap = snap->parent();
6761 }
6762 }
6763
6764 /* create a new diff for the hard disk being indirectly attached */
6765
6766 AutoLock baseHdLock (baseHd);
6767 baseHd->addReader();
6768
6769 ComObjPtr <HVirtualDiskImage> vdi;
6770 rc = baseHd->createDiffHardDisk (mUserData->mSnapshotFolderFull,
6771 mData->mUuid, vdi, NULL);
6772 baseHd->releaseReader();
6773 CheckComRCBreakRC (rc);
6774
6775 newDiffs.push_back (ComObjPtr <HardDisk> (vdi));
6776
6777 /* update the attachment and reset the dirty flag */
6778 hda->updateHardDisk (ComObjPtr <HardDisk> (vdi),
6779 false /* aDirty */);
6780 Log3 (("FC: %ls: diff created %ls\n",
6781 baseHd->toString().raw(), vdi->toString().raw()));
6782 }
6783 }
6784
6785 if (FAILED (rc))
6786 {
6787 /* delete diffs we created */
6788 for (std::list <ComObjPtr <HardDisk> >::const_iterator
6789 it = newDiffs.begin(); it != newDiffs.end(); ++ it)
6790 {
6791 /*
6792 * unregisterDiffHardDisk() is supposed to delete and uninit
6793 * the differencing hard disk
6794 */
6795 mParent->unregisterDiffHardDisk (*it);
6796 /* too bad if we fail here, but nothing to do, just continue */
6797 }
6798
6799 /* the best is to rollback the changes... */
6800 mHDData.rollback();
6801 mHDData->mHDAttachmentsChanged = false;
6802 Log3 (("FC: ROLLED BACK\n"));
6803 return rc;
6804 }
6805
6806 /*
6807 * go through the rest of old attachments and delete diffs
6808 * or deassociate hard disks from machines (they will become detached)
6809 */
6810 for (HDData::HDAttachmentList::iterator
6811 it = backedUp.begin(); it != backedUp.end(); ++ it)
6812 {
6813 ComObjPtr <HardDiskAttachment> hda = *it;
6814 ComObjPtr <HardDisk> hd = hda->hardDisk();
6815 AutoLock hdLock (hd);
6816
6817 if (hd->isDifferencing())
6818 {
6819 /*
6820 * unregisterDiffHardDisk() is supposed to delete and uninit
6821 * the differencing hard disk
6822 */
6823 Log3 (("FC: %ls diff deleted\n", hd->toString().raw()));
6824 rc = mParent->unregisterDiffHardDisk (hd);
6825 /*
6826 * too bad if we fail here, but nothing to do, just continue
6827 * (the last rc will be returned to the caller though)
6828 */
6829 }
6830 else
6831 {
6832 /* deassociate from this machine */
6833 Log3 (("FC: %ls deassociated\n", hd->toString().raw()));
6834 hd->setMachineId (Guid());
6835 }
6836 }
6837
6838 /* commit all the changes */
6839 mHDData->mHDAttachmentsChanged = mHDData.hasActualChanges();
6840 mHDData.commit();
6841 Log3 (("FC: COMMITTED\n"));
6842
6843 return rc;
6844 }
6845
6846 /*
6847 * changes are being rolled back,
6848 * go trhough all current attachments and fix up dirty ones
6849 * the way it is done in DetachHardDisk()
6850 */
6851
6852 for (HDData::HDAttachmentList::iterator it = mHDData->mHDAttachments.begin();
6853 it != mHDData->mHDAttachments.end();
6854 ++ it)
6855 {
6856 ComObjPtr <HardDiskAttachment> hda = *it;
6857 ComObjPtr <HardDisk> hd = hda->hardDisk();
6858 AutoLock hdLock (hd);
6859
6860 if (hda->isDirty())
6861 {
6862 switch (hd->type())
6863 {
6864 case HardDiskType_ImmutableHardDisk:
6865 {
6866 /* decrease readers increased in AttachHardDisk() */
6867 hd->releaseReader();
6868 Log3 (("FR: %ls released\n", hd->toString().raw()));
6869 break;
6870 }
6871 case HardDiskType_WritethroughHardDisk:
6872 {
6873 /* deassociate from this machine */
6874 hd->setMachineId (Guid());
6875 Log3 (("FR: %ls deassociated\n", hd->toString().raw()));
6876 break;
6877 }
6878 case HardDiskType_NormalHardDisk:
6879 {
6880 if (hd->snapshotId().isEmpty())
6881 {
6882 /* deassociate from this machine */
6883 hd->setMachineId (Guid());
6884 Log3 (("FR: %ls deassociated\n", hd->toString().raw()));
6885 }
6886 else
6887 {
6888 /* decrease readers increased in AttachHardDisk() */
6889 hd->releaseReader();
6890 Log3 (("FR: %ls released\n", hd->toString().raw()));
6891 }
6892
6893 break;
6894 }
6895 }
6896 }
6897 }
6898
6899 /* rollback all the changes */
6900 mHDData.rollback();
6901 Log3 (("FR: ROLLED BACK\n"));
6902
6903 return S_OK;
6904}
6905
6906/**
6907 * Creates differencing hard disks for all normal hard disks
6908 * and replaces attachments to refer to created disks.
6909 * Used when taking a snapshot or when discarding the current state.
6910 *
6911 * @param aSnapshotId ID of the snapshot being taken
6912 * or NULL if the current state is being discarded
6913 * @param aFolder folder where to create diff. hard disks
6914 * @param aProgress progress object to run (must contain at least as
6915 * many operations left as the number of VDIs attached)
6916 * @param aOnline whether the machine is online (i.e., when the EMT
6917 * thread is paused, OR when current hard disks are
6918 * marked as busy for some other reason)
6919 *
6920 * @note
6921 * The progress object is not marked as completed, neither on success
6922 * nor on failure. This is a responsibility of the caller.
6923 *
6924 * @note Locks mParent + this object for writing
6925 */
6926HRESULT Machine::createSnapshotDiffs (const Guid *aSnapshotId,
6927 const Bstr &aFolder,
6928 const ComObjPtr <Progress> &aProgress,
6929 bool aOnline)
6930{
6931 AssertReturn (!aFolder.isEmpty(), E_FAIL);
6932
6933 AutoCaller autoCaller (this);
6934 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
6935
6936 /* accessing mParent methods below needs mParent lock */
6937 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
6938
6939 HRESULT rc = S_OK;
6940
6941 // first pass: check accessibility before performing changes
6942 if (!aOnline)
6943 {
6944 for (HDData::HDAttachmentList::const_iterator it = mHDData->mHDAttachments.begin();
6945 it != mHDData->mHDAttachments.end();
6946 ++ it)
6947 {
6948 ComObjPtr <HardDiskAttachment> hda = *it;
6949 ComObjPtr <HardDisk> hd = hda->hardDisk();
6950 AutoLock hdLock (hd);
6951
6952 ComAssertMsgBreak (hd->type() == HardDiskType_NormalHardDisk,
6953 ("Invalid hard disk type %d\n", hd->type()),
6954 rc = E_FAIL);
6955
6956 ComAssertMsgBreak (!hd->isParentImmutable() ||
6957 hd->storageType() == HardDiskStorageType_VirtualDiskImage,
6958 ("Invalid hard disk storage type %d\n", hd->storageType()),
6959 rc = E_FAIL);
6960
6961 Bstr accessError;
6962 rc = hd->getAccessible (accessError);
6963 CheckComRCBreakRC (rc);
6964
6965 if (!accessError.isNull())
6966 {
6967 rc = setError (E_FAIL,
6968 tr ("Hard disk '%ls' is not accessible (%ls)"),
6969 hd->toString().raw(), accessError.raw());
6970 break;
6971 }
6972 }
6973 CheckComRCReturnRC (rc);
6974 }
6975
6976 HDData::HDAttachmentList attachments;
6977
6978 // second pass: perform changes
6979 for (HDData::HDAttachmentList::const_iterator it = mHDData->mHDAttachments.begin();
6980 it != mHDData->mHDAttachments.end();
6981 ++ it)
6982 {
6983 ComObjPtr <HardDiskAttachment> hda = *it;
6984 ComObjPtr <HardDisk> hd = hda->hardDisk();
6985 AutoLock hdLock (hd);
6986
6987 ComObjPtr <HardDisk> parent = hd->parent();
6988 AutoLock parentHdLock (parent);
6989
6990 ComObjPtr <HardDisk> newHd;
6991
6992 // clear busy flag if the VM is online
6993 if (aOnline)
6994 hd->clearBusy();
6995 // increase readers
6996 hd->addReader();
6997
6998 if (hd->isParentImmutable())
6999 {
7000 aProgress->advanceOperation (Bstr (Utf8StrFmt (
7001 tr ("Preserving immutable hard disk '%ls'"),
7002 parent->toString (true /* aShort */).raw())));
7003
7004 parentHdLock.unlock();
7005 alock.leave();
7006
7007 // create a copy of the independent diff
7008 ComObjPtr <HVirtualDiskImage> vdi;
7009 rc = hd->asVDI()->cloneDiffImage (aFolder, mData->mUuid, vdi,
7010 aProgress);
7011 newHd = vdi;
7012
7013 alock.enter();
7014 parentHdLock.lock();
7015
7016 // decrease readers (hd is no more used for reading in any case)
7017 hd->releaseReader();
7018 }
7019 else
7020 {
7021 // checked in the first pass
7022 Assert (hd->type() == HardDiskType_NormalHardDisk);
7023
7024 aProgress->advanceOperation (Bstr (Utf8StrFmt (
7025 tr ("Creating a differencing hard disk for '%ls'"),
7026 hd->root()->toString (true /* aShort */).raw())));
7027
7028 parentHdLock.unlock();
7029 alock.leave();
7030
7031 // create a new diff for the image being attached
7032 ComObjPtr <HVirtualDiskImage> vdi;
7033 rc = hd->createDiffHardDisk (aFolder, mData->mUuid, vdi, aProgress);
7034 newHd = vdi;
7035
7036 alock.enter();
7037 parentHdLock.lock();
7038
7039 if (SUCCEEDED (rc))
7040 {
7041 // if online, hd must keep a reader referece
7042 if (!aOnline)
7043 hd->releaseReader();
7044 }
7045 else
7046 {
7047 // decrease readers
7048 hd->releaseReader();
7049 }
7050 }
7051
7052 if (SUCCEEDED (rc))
7053 {
7054 ComObjPtr <HardDiskAttachment> newHda;
7055 newHda.createObject();
7056 rc = newHda->init (newHd, hda->controller(), hda->deviceNumber(),
7057 false /* aDirty */);
7058
7059 if (SUCCEEDED (rc))
7060 {
7061 // associate the snapshot id with the old hard disk
7062 if (hd->type() != HardDiskType_WritethroughHardDisk && aSnapshotId)
7063 hd->setSnapshotId (*aSnapshotId);
7064
7065 // add the new attachment
7066 attachments.push_back (newHda);
7067
7068 // if online, newHd must be marked as busy
7069 if (aOnline)
7070 newHd->setBusy();
7071 }
7072 }
7073
7074 if (FAILED (rc))
7075 {
7076 // set busy flag back if the VM is online
7077 if (aOnline)
7078 hd->setBusy();
7079 break;
7080 }
7081 }
7082
7083 if (SUCCEEDED (rc))
7084 {
7085 // replace the whole list of attachments with the new one
7086 mHDData->mHDAttachments = attachments;
7087 }
7088 else
7089 {
7090 // delete those diffs we've just created
7091 for (HDData::HDAttachmentList::const_iterator it = attachments.begin();
7092 it != attachments.end();
7093 ++ it)
7094 {
7095 ComObjPtr <HardDisk> hd = (*it)->hardDisk();
7096 AutoLock hdLock (hd);
7097 Assert (hd->children().size() == 0);
7098 Assert (hd->isDifferencing());
7099 // unregisterDiffHardDisk() is supposed to delete and uninit
7100 // the differencing hard disk
7101 mParent->unregisterDiffHardDisk (hd);
7102 }
7103 }
7104
7105 return rc;
7106}
7107
7108/**
7109 * Deletes differencing hard disks created by createSnapshotDiffs() in case
7110 * if snapshot creation was failed.
7111 *
7112 * @param aSnapshot failed snapshot
7113 *
7114 * @note Locks mParent + this object for writing.
7115 */
7116HRESULT Machine::deleteSnapshotDiffs (const ComObjPtr <Snapshot> &aSnapshot)
7117{
7118 AssertReturn (!aSnapshot.isNull(), E_FAIL);
7119
7120 AutoCaller autoCaller (this);
7121 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7122
7123 /* accessing mParent methods below needs mParent lock */
7124 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
7125
7126 /* short cut: check whether attachments are all the same */
7127 if (mHDData->mHDAttachments == aSnapshot->data().mMachine->mHDData->mHDAttachments)
7128 return S_OK;
7129
7130 HRESULT rc = S_OK;
7131
7132 for (HDData::HDAttachmentList::const_iterator it = mHDData->mHDAttachments.begin();
7133 it != mHDData->mHDAttachments.end();
7134 ++ it)
7135 {
7136 ComObjPtr <HardDiskAttachment> hda = *it;
7137 ComObjPtr <HardDisk> hd = hda->hardDisk();
7138 AutoLock hdLock (hd);
7139
7140 ComObjPtr <HardDisk> parent = hd->parent();
7141 AutoLock parentHdLock (parent);
7142
7143 if (!parent || parent->snapshotId() != aSnapshot->data().mId)
7144 continue;
7145
7146 /* must not have children */
7147 ComAssertRet (hd->children().size() == 0, E_FAIL);
7148
7149 /* deassociate the old hard disk from the given snapshot's ID */
7150 parent->setSnapshotId (Guid());
7151
7152 /* unregisterDiffHardDisk() is supposed to delete and uninit
7153 * the differencing hard disk */
7154 rc = mParent->unregisterDiffHardDisk (hd);
7155 /* continue on error */
7156 }
7157
7158 /* restore the whole list of attachments from the failed snapshot */
7159 mHDData->mHDAttachments = aSnapshot->data().mMachine->mHDData->mHDAttachments;
7160
7161 return rc;
7162}
7163
7164/**
7165 * Helper to lock the machine configuration for write access.
7166 *
7167 * @return S_OK or E_FAIL and sets error info on failure
7168 *
7169 * @note Doesn't lock anything (must be called from this object's lock)
7170 */
7171HRESULT Machine::lockConfig()
7172{
7173 HRESULT rc = S_OK;
7174
7175 if (!isConfigLocked())
7176 {
7177 /* open the associated config file */
7178 int vrc = RTFileOpen (&mData->mHandleCfgFile,
7179 Utf8Str (mData->mConfigFileFull),
7180 RTFILE_O_READWRITE | RTFILE_O_OPEN |
7181 RTFILE_O_DENY_WRITE);
7182 if (VBOX_FAILURE (vrc))
7183 mData->mHandleCfgFile = NIL_RTFILE;
7184 }
7185
7186 LogFlowThisFunc (("mConfigFile={%ls}, mHandleCfgFile=%d, rc=%08X\n",
7187 mData->mConfigFileFull.raw(), mData->mHandleCfgFile, rc));
7188 return rc;
7189}
7190
7191/**
7192 * Helper to unlock the machine configuration from write access
7193 *
7194 * @return S_OK
7195 *
7196 * @note Doesn't lock anything.
7197 * @note Not thread safe (must be called from this object's lock).
7198 */
7199HRESULT Machine::unlockConfig()
7200{
7201 HRESULT rc = S_OK;
7202
7203 if (isConfigLocked())
7204 {
7205 RTFileFlush(mData->mHandleCfgFile);
7206 RTFileClose(mData->mHandleCfgFile);
7207 /** @todo flush the directory. */
7208 mData->mHandleCfgFile = NIL_RTFILE;
7209 }
7210
7211 LogFlowThisFunc (("\n"));
7212
7213 return rc;
7214}
7215
7216/**
7217 * Returns true if the settings file is located in the directory named exactly
7218 * as the machine. This will be true if the machine settings structure was
7219 * created by default in #openConfigLoader().
7220 *
7221 * @param aSettingsDir if not NULL, the full machine settings file directory
7222 * name will be assigned there.
7223 *
7224 * @note Doesn't lock anything.
7225 * @note Not thread safe (must be called from this object's lock).
7226 */
7227bool Machine::isInOwnDir (Utf8Str *aSettingsDir /* = NULL */)
7228{
7229 Utf8Str settingsDir = mData->mConfigFileFull;
7230 RTPathStripFilename (settingsDir.mutableRaw());
7231 char *dirName = RTPathFilename (settingsDir);
7232
7233 AssertReturn (dirName, false);
7234
7235 /* if we don't rename anything on name change, return false shorlty */
7236 if (!mUserData->mNameSync)
7237 return false;
7238
7239 if (aSettingsDir)
7240 *aSettingsDir = settingsDir;
7241
7242 return Bstr (dirName) == mUserData->mName;
7243}
7244
7245/**
7246 * @note Locks objects for reading!
7247 */
7248bool Machine::isModified()
7249{
7250 AutoCaller autoCaller (this);
7251 AssertComRCReturn (autoCaller.rc(), false);
7252
7253 AutoReaderLock alock (this);
7254
7255 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
7256 if (mNetworkAdapters [slot] && mNetworkAdapters [slot]->isModified())
7257 return true;
7258
7259 for (ULONG slot = 0; slot < ELEMENTS (mSerialPorts); slot ++)
7260 if (mSerialPorts [slot] && mSerialPorts [slot]->isModified())
7261 return true;
7262
7263 for (ULONG slot = 0; slot < ELEMENTS (mParallelPorts); slot ++)
7264 if (mParallelPorts [slot] && mParallelPorts [slot]->isModified())
7265 return true;
7266
7267 return
7268 mUserData.isBackedUp() ||
7269 mHWData.isBackedUp() ||
7270 mHDData.isBackedUp() ||
7271#ifdef VBOX_VRDP
7272 (mVRDPServer && mVRDPServer->isModified()) ||
7273#endif
7274 (mDVDDrive && mDVDDrive->isModified()) ||
7275 (mFloppyDrive && mFloppyDrive->isModified()) ||
7276 (mAudioAdapter && mAudioAdapter->isModified()) ||
7277 (mUSBController && mUSBController->isModified()) ||
7278 (mBIOSSettings && mBIOSSettings->isModified());
7279}
7280
7281/**
7282 * @note This method doesn't check (ignores) actual changes to mHDData.
7283 * Use mHDData.mHDAttachmentsChanged right after #commit() instead.
7284 *
7285 * @param aIgnoreUserData |true| to ignore changes to mUserData
7286 *
7287 * @note Locks objects for reading!
7288 */
7289bool Machine::isReallyModified (bool aIgnoreUserData /* = false */)
7290{
7291 AutoCaller autoCaller (this);
7292 AssertComRCReturn (autoCaller.rc(), false);
7293
7294 AutoReaderLock alock (this);
7295
7296 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
7297 if (mNetworkAdapters [slot] && mNetworkAdapters [slot]->isReallyModified())
7298 return true;
7299
7300 for (ULONG slot = 0; slot < ELEMENTS (mSerialPorts); slot ++)
7301 if (mSerialPorts [slot] && mSerialPorts [slot]->isReallyModified())
7302 return true;
7303
7304 for (ULONG slot = 0; slot < ELEMENTS (mParallelPorts); slot ++)
7305 if (mParallelPorts [slot] && mParallelPorts [slot]->isReallyModified())
7306 return true;
7307
7308 return
7309 (!aIgnoreUserData && mUserData.hasActualChanges()) ||
7310 mHWData.hasActualChanges() ||
7311 /* ignore mHDData */
7312 //mHDData.hasActualChanges() ||
7313#ifdef VBOX_VRDP
7314 (mVRDPServer && mVRDPServer->isReallyModified()) ||
7315#endif
7316 (mDVDDrive && mDVDDrive->isReallyModified()) ||
7317 (mFloppyDrive && mFloppyDrive->isReallyModified()) ||
7318 (mAudioAdapter && mAudioAdapter->isReallyModified()) ||
7319 (mUSBController && mUSBController->isReallyModified()) ||
7320 (mBIOSSettings && mBIOSSettings->isReallyModified());
7321}
7322
7323/**
7324 * Discards all changes to machine settings.
7325 *
7326 * @param aNotify whether to notify the direct session about changes or not
7327 *
7328 * @note Locks objects!
7329 */
7330void Machine::rollback (bool aNotify)
7331{
7332 AutoCaller autoCaller (this);
7333 AssertComRCReturn (autoCaller.rc(), (void) 0);
7334
7335 AutoLock alock (this);
7336
7337 /* check for changes in own data */
7338
7339 bool sharedFoldersChanged = false;
7340
7341 if (aNotify && mHWData.isBackedUp())
7342 {
7343 if (mHWData->mSharedFolders.size() !=
7344 mHWData.backedUpData()->mSharedFolders.size())
7345 sharedFoldersChanged = true;
7346 else
7347 {
7348 for (HWData::SharedFolderList::iterator rit =
7349 mHWData->mSharedFolders.begin();
7350 rit != mHWData->mSharedFolders.end() && !sharedFoldersChanged;
7351 ++ rit)
7352 {
7353 for (HWData::SharedFolderList::iterator cit =
7354 mHWData.backedUpData()->mSharedFolders.begin();
7355 cit != mHWData.backedUpData()->mSharedFolders.end();
7356 ++ cit)
7357 {
7358 if ((*cit)->name() != (*rit)->name() ||
7359 (*cit)->hostPath() != (*rit)->hostPath())
7360 {
7361 sharedFoldersChanged = true;
7362 break;
7363 }
7364 }
7365 }
7366 }
7367 }
7368
7369 mUserData.rollback();
7370
7371 mHWData.rollback();
7372
7373 if (mHDData.isBackedUp())
7374 fixupHardDisks (false /* aCommit */);
7375
7376 /* check for changes in child objects */
7377
7378 bool vrdpChanged = false, dvdChanged = false, floppyChanged = false,
7379 usbChanged = false;
7380
7381 ComPtr <INetworkAdapter> networkAdapters [ELEMENTS (mNetworkAdapters)];
7382 ComPtr <ISerialPort> serialPorts [ELEMENTS (mSerialPorts)];
7383 ComPtr <IParallelPort> parallelPorts [ELEMENTS (mParallelPorts)];
7384
7385 if (mBIOSSettings)
7386 mBIOSSettings->rollback();
7387
7388#ifdef VBOX_VRDP
7389 if (mVRDPServer)
7390 vrdpChanged = mVRDPServer->rollback();
7391#endif
7392
7393 if (mDVDDrive)
7394 dvdChanged = mDVDDrive->rollback();
7395
7396 if (mFloppyDrive)
7397 floppyChanged = mFloppyDrive->rollback();
7398
7399 if (mAudioAdapter)
7400 mAudioAdapter->rollback();
7401
7402 if (mUSBController)
7403 usbChanged = mUSBController->rollback();
7404
7405 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
7406 if (mNetworkAdapters [slot])
7407 if (mNetworkAdapters [slot]->rollback())
7408 networkAdapters [slot] = mNetworkAdapters [slot];
7409
7410 for (ULONG slot = 0; slot < ELEMENTS (mSerialPorts); slot ++)
7411 if (mSerialPorts [slot])
7412 if (mSerialPorts [slot]->rollback())
7413 serialPorts [slot] = mSerialPorts [slot];
7414
7415 for (ULONG slot = 0; slot < ELEMENTS (mParallelPorts); slot ++)
7416 if (mParallelPorts [slot])
7417 if (mParallelPorts [slot]->rollback())
7418 parallelPorts [slot] = mParallelPorts [slot];
7419
7420 if (aNotify)
7421 {
7422 /* inform the direct session about changes */
7423
7424 ComObjPtr <Machine> that = this;
7425 alock.leave();
7426
7427 if (sharedFoldersChanged)
7428 that->onSharedFolderChange();
7429
7430 if (vrdpChanged)
7431 that->onVRDPServerChange();
7432 if (dvdChanged)
7433 that->onDVDDriveChange();
7434 if (floppyChanged)
7435 that->onFloppyDriveChange();
7436 if (usbChanged)
7437 that->onUSBControllerChange();
7438
7439 for (ULONG slot = 0; slot < ELEMENTS (networkAdapters); slot ++)
7440 if (networkAdapters [slot])
7441 that->onNetworkAdapterChange (networkAdapters [slot]);
7442 for (ULONG slot = 0; slot < ELEMENTS (serialPorts); slot ++)
7443 if (serialPorts [slot])
7444 that->onSerialPortChange (serialPorts [slot]);
7445 for (ULONG slot = 0; slot < ELEMENTS (parallelPorts); slot ++)
7446 if (parallelPorts [slot])
7447 that->onParallelPortChange (parallelPorts [slot]);
7448 }
7449}
7450
7451/**
7452 * Commits all the changes to machine settings.
7453 *
7454 * Note that when committing fails at some stage, it still continues
7455 * until the end. So, all data will either be actually committed or rolled
7456 * back (for failed cases) and the returned result code will describe the
7457 * first failure encountered. However, #isModified() will still return true
7458 * in case of failure, to indicade that settings in memory and on disk are
7459 * out of sync.
7460 *
7461 * @note Locks objects!
7462 */
7463HRESULT Machine::commit()
7464{
7465 AutoCaller autoCaller (this);
7466 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7467
7468 AutoLock alock (this);
7469
7470 HRESULT rc = S_OK;
7471
7472 /*
7473 * use safe commit to ensure Snapshot machines (that share mUserData)
7474 * will still refer to a valid memory location
7475 */
7476 mUserData.commitCopy();
7477
7478 mHWData.commit();
7479
7480 if (mHDData.isBackedUp())
7481 rc = fixupHardDisks (true /* aCommit */);
7482
7483 mBIOSSettings->commit();
7484#ifdef VBOX_VRDP
7485 mVRDPServer->commit();
7486#endif
7487 mDVDDrive->commit();
7488 mFloppyDrive->commit();
7489 mAudioAdapter->commit();
7490 mUSBController->commit();
7491
7492 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
7493 mNetworkAdapters [slot]->commit();
7494 for (ULONG slot = 0; slot < ELEMENTS (mSerialPorts); slot ++)
7495 mSerialPorts [slot]->commit();
7496 for (ULONG slot = 0; slot < ELEMENTS (mParallelPorts); slot ++)
7497 mParallelPorts [slot]->commit();
7498
7499 if (mType == IsSessionMachine)
7500 {
7501 /* attach new data to the primary machine and reshare it */
7502 mPeer->mUserData.attach (mUserData);
7503 mPeer->mHWData.attach (mHWData);
7504 mPeer->mHDData.attach (mHDData);
7505 }
7506
7507 if (FAILED (rc))
7508 {
7509 /*
7510 * backup arbitrary data item to cause #isModified() to still return
7511 * true in case of any error
7512 */
7513 mHWData.backup();
7514 }
7515
7516 return rc;
7517}
7518
7519/**
7520 * Copies all the hardware data from the given machine.
7521 *
7522 * @note
7523 * This method must be called from under this object's lock.
7524 * @note
7525 * This method doesn't call #commit(), so all data remains backed up
7526 * and unsaved.
7527 */
7528void Machine::copyFrom (Machine *aThat)
7529{
7530 AssertReturn (mType == IsMachine || mType == IsSessionMachine, (void) 0);
7531 AssertReturn (aThat->mType == IsSnapshotMachine, (void) 0);
7532
7533 mHWData.assignCopy (aThat->mHWData);
7534
7535 // create copies of all shared folders (mHWData after attiching a copy
7536 // contains just references to original objects)
7537 for (HWData::SharedFolderList::iterator it = mHWData->mSharedFolders.begin();
7538 it != mHWData->mSharedFolders.end();
7539 ++ it)
7540 {
7541 ComObjPtr <SharedFolder> folder;
7542 folder.createObject();
7543 HRESULT rc = folder->initCopy (machine(), *it);
7544 AssertComRC (rc);
7545 *it = folder;
7546 }
7547
7548 mBIOSSettings->copyFrom (aThat->mBIOSSettings);
7549#ifdef VBOX_VRDP
7550 mVRDPServer->copyFrom (aThat->mVRDPServer);
7551#endif
7552 mDVDDrive->copyFrom (aThat->mDVDDrive);
7553 mFloppyDrive->copyFrom (aThat->mFloppyDrive);
7554 mAudioAdapter->copyFrom (aThat->mAudioAdapter);
7555 mUSBController->copyFrom (aThat->mUSBController);
7556
7557 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
7558 mNetworkAdapters [slot]->copyFrom (aThat->mNetworkAdapters [slot]);
7559 for (ULONG slot = 0; slot < ELEMENTS (mSerialPorts); slot ++)
7560 mSerialPorts [slot]->copyFrom (aThat->mSerialPorts [slot]);
7561 for (ULONG slot = 0; slot < ELEMENTS (mParallelPorts); slot ++)
7562 mParallelPorts [slot]->copyFrom (aThat->mParallelPorts [slot]);
7563}
7564
7565/////////////////////////////////////////////////////////////////////////////
7566// SessionMachine class
7567/////////////////////////////////////////////////////////////////////////////
7568
7569/** Task structure for asynchronous VM operations */
7570struct SessionMachine::Task
7571{
7572 Task (SessionMachine *m, Progress *p)
7573 : machine (m), progress (p)
7574 , state (m->data()->mMachineState) // save the current machine state
7575 , subTask (false), settingsChanged (false)
7576 {}
7577
7578 void modifyLastState (MachineState_T s)
7579 {
7580 *const_cast <MachineState_T *> (&state) = s;
7581 }
7582
7583 virtual void handler() = 0;
7584
7585 const ComObjPtr <SessionMachine> machine;
7586 const ComObjPtr <Progress> progress;
7587 const MachineState_T state;
7588
7589 bool subTask : 1;
7590 bool settingsChanged : 1;
7591};
7592
7593/** Take snapshot task */
7594struct SessionMachine::TakeSnapshotTask : public SessionMachine::Task
7595{
7596 TakeSnapshotTask (SessionMachine *m)
7597 : Task (m, NULL) {}
7598
7599 void handler() { machine->takeSnapshotHandler (*this); }
7600};
7601
7602/** Discard snapshot task */
7603struct SessionMachine::DiscardSnapshotTask : public SessionMachine::Task
7604{
7605 DiscardSnapshotTask (SessionMachine *m, Progress *p, Snapshot *s)
7606 : Task (m, p)
7607 , snapshot (s) {}
7608
7609 DiscardSnapshotTask (const Task &task, Snapshot *s)
7610 : Task (task)
7611 , snapshot (s) {}
7612
7613 void handler() { machine->discardSnapshotHandler (*this); }
7614
7615 const ComObjPtr <Snapshot> snapshot;
7616};
7617
7618/** Discard current state task */
7619struct SessionMachine::DiscardCurrentStateTask : public SessionMachine::Task
7620{
7621 DiscardCurrentStateTask (SessionMachine *m, Progress *p,
7622 bool discardCurSnapshot)
7623 : Task (m, p), discardCurrentSnapshot (discardCurSnapshot) {}
7624
7625 void handler() { machine->discardCurrentStateHandler (*this); }
7626
7627 const bool discardCurrentSnapshot;
7628};
7629
7630////////////////////////////////////////////////////////////////////////////////
7631
7632DEFINE_EMPTY_CTOR_DTOR (SessionMachine)
7633
7634HRESULT SessionMachine::FinalConstruct()
7635{
7636 LogFlowThisFunc (("\n"));
7637
7638 /* set the proper type to indicate we're the SessionMachine instance */
7639 unconst (mType) = IsSessionMachine;
7640
7641#if defined(RT_OS_WINDOWS)
7642 mIPCSem = NULL;
7643#elif defined(RT_OS_OS2)
7644 mIPCSem = NULLHANDLE;
7645#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
7646 mIPCSem = -1;
7647#else
7648# error "Port me!"
7649#endif
7650
7651 return S_OK;
7652}
7653
7654void SessionMachine::FinalRelease()
7655{
7656 LogFlowThisFunc (("\n"));
7657
7658 uninit (Uninit::Unexpected);
7659}
7660
7661/**
7662 * @note Must be called only by Machine::openSession() from its own write lock.
7663 */
7664HRESULT SessionMachine::init (Machine *aMachine)
7665{
7666 LogFlowThisFuncEnter();
7667 LogFlowThisFunc (("mName={%ls}\n", aMachine->mUserData->mName.raw()));
7668
7669 AssertReturn (aMachine, E_INVALIDARG);
7670
7671 AssertReturn (aMachine->lockHandle()->isLockedOnCurrentThread(), E_FAIL);
7672
7673 /* Enclose the state transition NotReady->InInit->Ready */
7674 AutoInitSpan autoInitSpan (this);
7675 AssertReturn (autoInitSpan.isOk(), E_UNEXPECTED);
7676
7677 /* create the interprocess semaphore */
7678#if defined(RT_OS_WINDOWS)
7679 mIPCSemName = aMachine->mData->mConfigFileFull;
7680 for (size_t i = 0; i < mIPCSemName.length(); i++)
7681 if (mIPCSemName[i] == '\\')
7682 mIPCSemName[i] = '/';
7683 mIPCSem = ::CreateMutex (NULL, FALSE, mIPCSemName);
7684 ComAssertMsgRet (mIPCSem,
7685 ("Cannot create IPC mutex '%ls', err=%d\n",
7686 mIPCSemName.raw(), ::GetLastError()),
7687 E_FAIL);
7688#elif defined(RT_OS_OS2)
7689 Utf8Str ipcSem = Utf8StrFmt ("\\SEM32\\VBOX\\VM\\{%Vuuid}",
7690 aMachine->mData->mUuid.raw());
7691 mIPCSemName = ipcSem;
7692 APIRET arc = ::DosCreateMutexSem ((PSZ) ipcSem.raw(), &mIPCSem, 0, FALSE);
7693 ComAssertMsgRet (arc == NO_ERROR,
7694 ("Cannot create IPC mutex '%s', arc=%ld\n",
7695 ipcSem.raw(), arc),
7696 E_FAIL);
7697#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
7698 Utf8Str configFile = aMachine->mData->mConfigFileFull;
7699 char *configFileCP = NULL;
7700 RTStrUtf8ToCurrentCP (&configFileCP, configFile);
7701 key_t key = ::ftok (configFileCP, 0);
7702 RTStrFree (configFileCP);
7703 mIPCSem = ::semget (key, 1, S_IRWXU | S_IRWXG | S_IRWXO | IPC_CREAT);
7704 ComAssertMsgRet (mIPCSem >= 0, ("Cannot create IPC semaphore, errno=%d", errno),
7705 E_FAIL);
7706 /* set the initial value to 1 */
7707 int rv = ::semctl (mIPCSem, 0, SETVAL, 1);
7708 ComAssertMsgRet (rv == 0, ("Cannot init IPC semaphore, errno=%d", errno),
7709 E_FAIL);
7710#else
7711# error "Port me!"
7712#endif
7713
7714 /* memorize the peer Machine */
7715 unconst (mPeer) = aMachine;
7716 /* share the parent pointer */
7717 unconst (mParent) = aMachine->mParent;
7718
7719 /* take the pointers to data to share */
7720 mData.share (aMachine->mData);
7721 mSSData.share (aMachine->mSSData);
7722
7723 mUserData.share (aMachine->mUserData);
7724 mHWData.share (aMachine->mHWData);
7725 mHDData.share (aMachine->mHDData);
7726
7727 unconst (mBIOSSettings).createObject();
7728 mBIOSSettings->init (this, aMachine->mBIOSSettings);
7729#ifdef VBOX_VRDP
7730 /* create another VRDPServer object that will be mutable */
7731 unconst (mVRDPServer).createObject();
7732 mVRDPServer->init (this, aMachine->mVRDPServer);
7733#endif
7734 /* create another DVD drive object that will be mutable */
7735 unconst (mDVDDrive).createObject();
7736 mDVDDrive->init (this, aMachine->mDVDDrive);
7737 /* create another floppy drive object that will be mutable */
7738 unconst (mFloppyDrive).createObject();
7739 mFloppyDrive->init (this, aMachine->mFloppyDrive);
7740 /* create another audio adapter object that will be mutable */
7741 unconst (mAudioAdapter).createObject();
7742 mAudioAdapter->init (this, aMachine->mAudioAdapter);
7743 /* create a list of serial ports that will be mutable */
7744 for (ULONG slot = 0; slot < ELEMENTS (mSerialPorts); slot ++)
7745 {
7746 unconst (mSerialPorts [slot]).createObject();
7747 mSerialPorts [slot]->init (this, aMachine->mSerialPorts [slot]);
7748 }
7749 /* create a list of parallel ports that will be mutable */
7750 for (ULONG slot = 0; slot < ELEMENTS (mParallelPorts); slot ++)
7751 {
7752 unconst (mParallelPorts [slot]).createObject();
7753 mParallelPorts [slot]->init (this, aMachine->mParallelPorts [slot]);
7754 }
7755 /* create another USB controller object that will be mutable */
7756 unconst (mUSBController).createObject();
7757 mUSBController->init (this, aMachine->mUSBController);
7758 /* create a list of network adapters that will be mutable */
7759 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
7760 {
7761 unconst (mNetworkAdapters [slot]).createObject();
7762 mNetworkAdapters [slot]->init (this, aMachine->mNetworkAdapters [slot]);
7763 }
7764
7765 /* Confirm a successful initialization when it's the case */
7766 autoInitSpan.setSucceeded();
7767
7768 LogFlowThisFuncLeave();
7769 return S_OK;
7770}
7771
7772/**
7773 * Uninitializes this session object. If the reason is other than
7774 * Uninit::Unexpected, then this method MUST be called from #checkForDeath().
7775 *
7776 * @param aReason uninitialization reason
7777 *
7778 * @note Locks mParent + this object for writing.
7779 */
7780void SessionMachine::uninit (Uninit::Reason aReason)
7781{
7782 LogFlowThisFuncEnter();
7783 LogFlowThisFunc (("reason=%d\n", aReason));
7784
7785 /*
7786 * Strongly reference ourselves to prevent this object deletion after
7787 * mData->mSession.mMachine.setNull() below (which can release the last
7788 * reference and call the destructor). Important: this must be done before
7789 * accessing any members (and before AutoUninitSpan that does it as well).
7790 * This self reference will be released as the very last step on return.
7791 */
7792 ComObjPtr <SessionMachine> selfRef = this;
7793
7794 /* Enclose the state transition Ready->InUninit->NotReady */
7795 AutoUninitSpan autoUninitSpan (this);
7796 if (autoUninitSpan.uninitDone())
7797 {
7798 LogFlowThisFunc (("Already uninitialized\n"));
7799 LogFlowThisFuncLeave();
7800 return;
7801 }
7802
7803 if (autoUninitSpan.initFailed())
7804 {
7805 /*
7806 * We've been called by init() because it's failed. It's not really
7807 * necessary (nor it's safe) to perform the regular uninit sequence
7808 * below, the following is enough.
7809 */
7810 LogFlowThisFunc (("Initialization failed.\n"));
7811#if defined(RT_OS_WINDOWS)
7812 if (mIPCSem)
7813 ::CloseHandle (mIPCSem);
7814 mIPCSem = NULL;
7815#elif defined(RT_OS_OS2)
7816 if (mIPCSem != NULLHANDLE)
7817 ::DosCloseMutexSem (mIPCSem);
7818 mIPCSem = NULLHANDLE;
7819#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
7820 if (mIPCSem >= 0)
7821 ::semctl (mIPCSem, 0, IPC_RMID);
7822 mIPCSem = -1;
7823#else
7824# error "Port me!"
7825#endif
7826 uninitDataAndChildObjects();
7827 unconst (mParent).setNull();
7828 unconst (mPeer).setNull();
7829 LogFlowThisFuncLeave();
7830 return;
7831 }
7832
7833 /*
7834 * We need to lock this object in uninit() because the lock is shared
7835 * with mPeer (as well as data we modify below).
7836 * mParent->addProcessToReap() and others need mParent lock.
7837 */
7838 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
7839
7840 MachineState_T lastState = mData->mMachineState;
7841
7842 if (aReason == Uninit::Abnormal)
7843 {
7844 LogWarningThisFunc (("ABNORMAL client termination! (wasRunning=%d)\n",
7845 lastState >= MachineState_Running));
7846
7847 /* reset the state to Aborted */
7848 if (mData->mMachineState != MachineState_Aborted)
7849 setMachineState (MachineState_Aborted);
7850 }
7851
7852 if (isModified())
7853 {
7854 LogWarningThisFunc (("Discarding unsaved settings changes!\n"));
7855 rollback (false /* aNotify */);
7856 }
7857
7858 Assert (!mSnapshotData.mStateFilePath || !mSnapshotData.mSnapshot);
7859 if (mSnapshotData.mStateFilePath)
7860 {
7861 LogWarningThisFunc (("canceling failed save state request!\n"));
7862 endSavingState (FALSE /* aSuccess */);
7863 }
7864 else if (!!mSnapshotData.mSnapshot)
7865 {
7866 LogWarningThisFunc (("canceling untaken snapshot!\n"));
7867 endTakingSnapshot (FALSE /* aSuccess */);
7868 }
7869
7870 /* release all captured USB devices */
7871 if (aReason == Uninit::Abnormal && lastState >= MachineState_Running)
7872 {
7873 /* Console::captureUSBDevices() is called in the VM process only after
7874 * setting the machine state to Starting or Restoring.
7875 * Console::detachAllUSBDevices() will be called upon successful
7876 * termination. So, we need to release USB devices only if there was
7877 * an abnormal termination of a running VM. */
7878 DetachAllUSBDevices (TRUE /* aDone */);
7879 }
7880
7881 if (!mData->mSession.mType.isNull())
7882 {
7883 /* mType is not null when this machine's process has been started by
7884 * VirtualBox::OpenRemoteSession(), therefore it is our child. We
7885 * need to queue the PID to reap the process (and avoid zombies on
7886 * Linux). */
7887 Assert (mData->mSession.mPid != NIL_RTPROCESS);
7888 mParent->addProcessToReap (mData->mSession.mPid);
7889 }
7890
7891 mData->mSession.mPid = NIL_RTPROCESS;
7892
7893 if (aReason == Uninit::Unexpected)
7894 {
7895 /* Uninitialization didn't come from #checkForDeath(), so tell the
7896 * client watcher thread to update the set of machines that have open
7897 * sessions. */
7898 mParent->updateClientWatcher();
7899 }
7900
7901 /* uninitialize all remote controls */
7902 if (mData->mSession.mRemoteControls.size())
7903 {
7904 LogFlowThisFunc (("Closing remote sessions (%d):\n",
7905 mData->mSession.mRemoteControls.size()));
7906
7907 Data::Session::RemoteControlList::iterator it =
7908 mData->mSession.mRemoteControls.begin();
7909 while (it != mData->mSession.mRemoteControls.end())
7910 {
7911 LogFlowThisFunc ((" Calling remoteControl->Uninitialize()...\n"));
7912 HRESULT rc = (*it)->Uninitialize();
7913 LogFlowThisFunc ((" remoteControl->Uninitialize() returned %08X\n", rc));
7914 if (FAILED (rc))
7915 LogWarningThisFunc (("Forgot to close the remote session?\n"));
7916 ++ it;
7917 }
7918 mData->mSession.mRemoteControls.clear();
7919 }
7920
7921 /*
7922 * An expected uninitialization can come only from #checkForDeath().
7923 * Otherwise it means that something's got really wrong (for examlple,
7924 * the Session implementation has released the VirtualBox reference
7925 * before it triggered #OnSessionEnd(), or before releasing IPC semaphore,
7926 * etc). However, it's also possible, that the client releases the IPC
7927 * semaphore correctly (i.e. before it releases the VirtualBox reference),
7928 * but but the VirtualBox release event comes first to the server process.
7929 * This case is practically possible, so we should not assert on an
7930 * unexpected uninit, just log a warning.
7931 */
7932
7933 if ((aReason == Uninit::Unexpected))
7934 LogWarningThisFunc (("Unexpected SessionMachine uninitialization!\n"));
7935
7936 if (aReason != Uninit::Normal)
7937 mData->mSession.mDirectControl.setNull();
7938 else
7939 {
7940 /* this must be null here (see #OnSessionEnd()) */
7941 Assert (mData->mSession.mDirectControl.isNull());
7942 Assert (mData->mSession.mState == SessionState_SessionClosing);
7943 Assert (!mData->mSession.mProgress.isNull());
7944
7945 mData->mSession.mProgress->notifyComplete (S_OK);
7946 mData->mSession.mProgress.setNull();
7947 }
7948
7949 /* remove the association between the peer machine and this session machine */
7950 Assert (mData->mSession.mMachine == this ||
7951 aReason == Uninit::Unexpected);
7952
7953 /* reset the rest of session data */
7954 mData->mSession.mMachine.setNull();
7955 mData->mSession.mState = SessionState_SessionClosed;
7956 mData->mSession.mType.setNull();
7957
7958 /* close the interprocess semaphore before leaving the shared lock */
7959#if defined(RT_OS_WINDOWS)
7960 if (mIPCSem)
7961 ::CloseHandle (mIPCSem);
7962 mIPCSem = NULL;
7963#elif defined(RT_OS_OS2)
7964 if (mIPCSem != NULLHANDLE)
7965 ::DosCloseMutexSem (mIPCSem);
7966 mIPCSem = NULLHANDLE;
7967#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
7968 if (mIPCSem >= 0)
7969 ::semctl (mIPCSem, 0, IPC_RMID);
7970 mIPCSem = -1;
7971#else
7972# error "Port me!"
7973#endif
7974
7975 /* fire an event */
7976 mParent->onSessionStateChange (mData->mUuid, SessionState_SessionClosed);
7977
7978 uninitDataAndChildObjects();
7979
7980 /* leave the shared lock before setting the above two to NULL */
7981 alock.leave();
7982
7983 unconst (mParent).setNull();
7984 unconst (mPeer).setNull();
7985
7986 LogFlowThisFuncLeave();
7987}
7988
7989// AutoLock::Lockable interface
7990////////////////////////////////////////////////////////////////////////////////
7991
7992/**
7993 * Overrides VirtualBoxBase::lockHandle() in order to share the lock handle
7994 * with the primary Machine instance (mPeer).
7995 */
7996AutoLock::Handle *SessionMachine::lockHandle() const
7997{
7998 AssertReturn (!mPeer.isNull(), NULL);
7999 return mPeer->lockHandle();
8000}
8001
8002// IInternalMachineControl methods
8003////////////////////////////////////////////////////////////////////////////////
8004
8005/**
8006 * @note Locks the same as #setMachineState() does.
8007 */
8008STDMETHODIMP SessionMachine::UpdateState (MachineState_T machineState)
8009{
8010 return setMachineState (machineState);
8011}
8012
8013/**
8014 * @note Locks this object for reading.
8015 */
8016STDMETHODIMP SessionMachine::GetIPCId (BSTR *id)
8017{
8018 AutoCaller autoCaller (this);
8019 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8020
8021 AutoReaderLock alock (this);
8022
8023#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
8024 mIPCSemName.cloneTo (id);
8025 return S_OK;
8026#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
8027 mData->mConfigFileFull.cloneTo (id);
8028 return S_OK;
8029#else
8030# error "Port me!"
8031#endif
8032}
8033
8034/**
8035 * Goes through the USB filters of the given machine to see if the given
8036 * device matches any filter or not.
8037 *
8038 * @note Locks the same as USBController::hasMatchingFilter() does.
8039 */
8040STDMETHODIMP SessionMachine::RunUSBDeviceFilters (IUSBDevice *aUSBDevice,
8041 BOOL *aMatched)
8042{
8043 LogFlowThisFunc (("\n"));
8044
8045 if (!aUSBDevice)
8046 return E_INVALIDARG;
8047 if (!aMatched)
8048 return E_POINTER;
8049
8050 AutoCaller autoCaller (this);
8051 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8052
8053 *aMatched = mUSBController->hasMatchingFilter (aUSBDevice);
8054
8055 return S_OK;
8056}
8057
8058/**
8059 * @note Locks the same as Host::captureUSBDevice() does.
8060 */
8061STDMETHODIMP SessionMachine::CaptureUSBDevice (INPTR GUIDPARAM aId)
8062{
8063 LogFlowThisFunc (("\n"));
8064
8065 AutoCaller autoCaller (this);
8066 AssertComRCReturnRC (autoCaller.rc());
8067
8068 /* if cautureUSBDevice() fails, it must have set extended error info */
8069 return mParent->host()->captureUSBDevice (this, aId);
8070}
8071
8072/**
8073 * @note Locks the same as Host::detachUSBDevice() does.
8074 */
8075STDMETHODIMP SessionMachine::DetachUSBDevice (INPTR GUIDPARAM aId, BOOL aDone)
8076{
8077 LogFlowThisFunc (("\n"));
8078
8079 AutoCaller autoCaller (this);
8080 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8081
8082 return mParent->host()->detachUSBDevice (this, aId, aDone);
8083}
8084
8085/**
8086 * Inserts all machine filters to the USB proxy service and then calls
8087 * Host::autoCaptureUSBDevices().
8088 *
8089 * Called by Console from the VM process upon VM startup.
8090 *
8091 * @note Locks what called methods lock.
8092 */
8093STDMETHODIMP SessionMachine::AutoCaptureUSBDevices()
8094{
8095 LogFlowThisFunc (("\n"));
8096
8097 AutoCaller autoCaller (this);
8098 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8099
8100 HRESULT rc = mUSBController->notifyProxy (true /* aInsertFilters */);
8101 AssertComRC (rc);
8102 NOREF (rc);
8103
8104 return mParent->host()->autoCaptureUSBDevices (this);
8105}
8106
8107/**
8108 * Removes all machine filters from the USB proxy service and then calls
8109 * Host::detachAllUSBDevices().
8110 *
8111 * Called by Console from the VM process upon normal VM termination or by
8112 * SessionMachine::uninit() upon abnormal VM termination (from under the
8113 * Machine/SessionMachine lock).
8114 *
8115 * @note Locks what called methods lock.
8116 */
8117STDMETHODIMP SessionMachine::DetachAllUSBDevices(BOOL aDone)
8118{
8119 LogFlowThisFunc (("\n"));
8120
8121 AutoCaller autoCaller (this);
8122 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8123
8124 HRESULT rc = mUSBController->notifyProxy (false /* aInsertFilters */);
8125 AssertComRC (rc);
8126 NOREF (rc);
8127
8128 return mParent->host()->detachAllUSBDevices (this, aDone);
8129}
8130
8131/**
8132 * @note Locks mParent + this object for writing.
8133 */
8134STDMETHODIMP SessionMachine::OnSessionEnd (ISession *aSession,
8135 IProgress **aProgress)
8136{
8137 LogFlowThisFuncEnter();
8138
8139 AssertReturn (aSession, E_INVALIDARG);
8140 AssertReturn (aProgress, E_INVALIDARG);
8141
8142 AutoCaller autoCaller (this);
8143
8144 LogFlowThisFunc (("state=%d\n", autoCaller.state()));
8145 /*
8146 * We don't assert below because it might happen that a non-direct session
8147 * informs us it is closed right after we've been uninitialized -- it's ok.
8148 */
8149 CheckComRCReturnRC (autoCaller.rc());
8150
8151 /* get IInternalSessionControl interface */
8152 ComPtr <IInternalSessionControl> control (aSession);
8153
8154 ComAssertRet (!control.isNull(), E_INVALIDARG);
8155
8156 /* Progress::init() needs mParent lock */
8157 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
8158
8159 if (control.equalsTo (mData->mSession.mDirectControl))
8160 {
8161 ComAssertRet (aProgress, E_POINTER);
8162
8163 /* The direct session is being normally closed by the client process
8164 * ----------------------------------------------------------------- */
8165
8166 /* go to the closing state (essential for all open*Session() calls and
8167 * for #checkForDeath()) */
8168 Assert (mData->mSession.mState == SessionState_SessionOpen);
8169 mData->mSession.mState = SessionState_SessionClosing;
8170
8171 /* set direct control to NULL to release the remote instance */
8172 mData->mSession.mDirectControl.setNull();
8173 LogFlowThisFunc (("Direct control is set to NULL\n"));
8174
8175 /*
8176 * Create the progress object the client will use to wait until
8177 * #checkForDeath() is called to uninitialize this session object
8178 * after it releases the IPC semaphore.
8179 */
8180 ComObjPtr <Progress> progress;
8181 progress.createObject();
8182 progress->init (mParent, (IMachine *) mPeer, Bstr (tr ("Closing session")),
8183 FALSE /* aCancelable */);
8184 progress.queryInterfaceTo (aProgress);
8185 mData->mSession.mProgress = progress;
8186 }
8187 else
8188 {
8189 /* the remote session is being normally closed */
8190 Data::Session::RemoteControlList::iterator it =
8191 mData->mSession.mRemoteControls.begin();
8192 while (it != mData->mSession.mRemoteControls.end())
8193 {
8194 if (control.equalsTo (*it))
8195 break;
8196 ++it;
8197 }
8198 BOOL found = it != mData->mSession.mRemoteControls.end();
8199 ComAssertMsgRet (found, ("The session is not found in the session list!"),
8200 E_INVALIDARG);
8201 mData->mSession.mRemoteControls.remove (*it);
8202 }
8203
8204 LogFlowThisFuncLeave();
8205 return S_OK;
8206}
8207
8208/**
8209 * @note Locks mParent + this object for writing.
8210 */
8211STDMETHODIMP SessionMachine::BeginSavingState (IProgress *aProgress, BSTR *aStateFilePath)
8212{
8213 LogFlowThisFuncEnter();
8214
8215 AssertReturn (aProgress, E_INVALIDARG);
8216 AssertReturn (aStateFilePath, E_POINTER);
8217
8218 AutoCaller autoCaller (this);
8219 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8220
8221 /* mParent->addProgress() needs mParent lock */
8222 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
8223
8224 AssertReturn (mData->mMachineState == MachineState_Paused &&
8225 mSnapshotData.mLastState == MachineState_InvalidMachineState &&
8226 mSnapshotData.mProgressId.isEmpty() &&
8227 mSnapshotData.mStateFilePath.isNull(),
8228 E_FAIL);
8229
8230 /* memorize the progress ID and add it to the global collection */
8231 Guid progressId;
8232 HRESULT rc = aProgress->COMGETTER(Id) (progressId.asOutParam());
8233 AssertComRCReturn (rc, rc);
8234 rc = mParent->addProgress (aProgress);
8235 AssertComRCReturn (rc, rc);
8236
8237 Bstr stateFilePath;
8238 /* stateFilePath is null when the machine is not running */
8239 if (mData->mMachineState == MachineState_Paused)
8240 {
8241 stateFilePath = Utf8StrFmt ("%ls%c{%Vuuid}.sav",
8242 mUserData->mSnapshotFolderFull.raw(),
8243 RTPATH_DELIMITER, mData->mUuid.raw());
8244 }
8245
8246 /* fill in the snapshot data */
8247 mSnapshotData.mLastState = mData->mMachineState;
8248 mSnapshotData.mProgressId = progressId;
8249 mSnapshotData.mStateFilePath = stateFilePath;
8250
8251 /* set the state to Saving (this is expected by Console::SaveState()) */
8252 setMachineState (MachineState_Saving);
8253
8254 stateFilePath.cloneTo (aStateFilePath);
8255
8256 return S_OK;
8257}
8258
8259/**
8260 * @note Locks mParent + this objects for writing.
8261 */
8262STDMETHODIMP SessionMachine::EndSavingState (BOOL aSuccess)
8263{
8264 LogFlowThisFunc (("\n"));
8265
8266 AutoCaller autoCaller (this);
8267 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8268
8269 /* endSavingState() need mParent lock */
8270 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
8271
8272 AssertReturn (mData->mMachineState == MachineState_Saving &&
8273 mSnapshotData.mLastState != MachineState_InvalidMachineState &&
8274 !mSnapshotData.mProgressId.isEmpty() &&
8275 !mSnapshotData.mStateFilePath.isNull(),
8276 E_FAIL);
8277
8278 /*
8279 * on success, set the state to Saved;
8280 * on failure, set the state to the state we had when BeginSavingState() was
8281 * called (this is expected by Console::SaveState() and
8282 * Console::saveStateThread())
8283 */
8284 if (aSuccess)
8285 setMachineState (MachineState_Saved);
8286 else
8287 setMachineState (mSnapshotData.mLastState);
8288
8289 return endSavingState (aSuccess);
8290}
8291
8292/**
8293 * @note Locks mParent + this objects for writing.
8294 */
8295STDMETHODIMP SessionMachine::BeginTakingSnapshot (
8296 IConsole *aInitiator, INPTR BSTR aName, INPTR BSTR aDescription,
8297 IProgress *aProgress, BSTR *aStateFilePath,
8298 IProgress **aServerProgress)
8299{
8300 LogFlowThisFuncEnter();
8301
8302 AssertReturn (aInitiator && aName, E_INVALIDARG);
8303 AssertReturn (aStateFilePath && aServerProgress, E_POINTER);
8304
8305 LogFlowThisFunc (("aName='%ls'\n", aName));
8306
8307 AutoCaller autoCaller (this);
8308 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8309
8310 /* Progress::init() needs mParent lock */
8311 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
8312
8313 AssertReturn ((mData->mMachineState < MachineState_Running ||
8314 mData->mMachineState == MachineState_Paused) &&
8315 mSnapshotData.mLastState == MachineState_InvalidMachineState &&
8316 mSnapshotData.mSnapshot.isNull() &&
8317 mSnapshotData.mServerProgress.isNull() &&
8318 mSnapshotData.mCombinedProgress.isNull(),
8319 E_FAIL);
8320
8321 bool takingSnapshotOnline = mData->mMachineState == MachineState_Paused;
8322
8323 if (!takingSnapshotOnline && mData->mMachineState != MachineState_Saved)
8324 {
8325 /*
8326 * save all current settings to ensure current changes are committed
8327 * and hard disks are fixed up
8328 */
8329 HRESULT rc = saveSettings();
8330 CheckComRCReturnRC (rc);
8331 }
8332
8333 /* check that there are no Writethrough hard disks attached */
8334 for (HDData::HDAttachmentList::const_iterator
8335 it = mHDData->mHDAttachments.begin();
8336 it != mHDData->mHDAttachments.end();
8337 ++ it)
8338 {
8339 ComObjPtr <HardDisk> hd = (*it)->hardDisk();
8340 AutoLock hdLock (hd);
8341 if (hd->type() == HardDiskType_WritethroughHardDisk)
8342 return setError (E_FAIL,
8343 tr ("Cannot take a snapshot when there is a Writethrough hard "
8344 " disk attached ('%ls')"), hd->toString().raw());
8345 }
8346
8347 AssertReturn (aProgress || !takingSnapshotOnline, E_FAIL);
8348
8349 /* create an ID for the snapshot */
8350 Guid snapshotId;
8351 snapshotId.create();
8352
8353 Bstr stateFilePath;
8354 /* stateFilePath is null when the machine is not online nor saved */
8355 if (takingSnapshotOnline || mData->mMachineState == MachineState_Saved)
8356 stateFilePath = Utf8StrFmt ("%ls%c{%Vuuid}.sav",
8357 mUserData->mSnapshotFolderFull.raw(),
8358 RTPATH_DELIMITER,
8359 snapshotId.ptr());
8360
8361 /* ensure the directory for the saved state file exists */
8362 if (stateFilePath)
8363 {
8364 Utf8Str dir = stateFilePath;
8365 RTPathStripFilename (dir.mutableRaw());
8366 if (!RTDirExists (dir))
8367 {
8368 int vrc = RTDirCreateFullPath (dir, 0777);
8369 if (VBOX_FAILURE (vrc))
8370 return setError (E_FAIL,
8371 tr ("Could not create a directory '%s' to save the "
8372 "VM state to (%Vrc)"),
8373 dir.raw(), vrc);
8374 }
8375 }
8376
8377 /* create a snapshot machine object */
8378 ComObjPtr <SnapshotMachine> snapshotMachine;
8379 snapshotMachine.createObject();
8380 HRESULT rc = snapshotMachine->init (this, snapshotId, stateFilePath);
8381 AssertComRCReturn (rc, rc);
8382
8383 Bstr progressDesc = Bstr (tr ("Taking snapshot of virtual machine"));
8384 Bstr firstOpDesc = Bstr (tr ("Preparing to take snapshot"));
8385
8386 /*
8387 * create a server-side progress object (it will be descriptionless
8388 * when we need to combine it with the VM-side progress, i.e. when we're
8389 * taking a snapshot online). The number of operations is:
8390 * 1 (preparing) + # of VDIs + 1 (if the state is saved so we need to copy it)
8391 */
8392 ComObjPtr <Progress> serverProgress;
8393 {
8394 ULONG opCount = 1 + mHDData->mHDAttachments.size();
8395 if (mData->mMachineState == MachineState_Saved)
8396 opCount ++;
8397 serverProgress.createObject();
8398 if (takingSnapshotOnline)
8399 rc = serverProgress->init (FALSE, opCount, firstOpDesc);
8400 else
8401 rc = serverProgress->init (mParent, aInitiator, progressDesc, FALSE,
8402 opCount, firstOpDesc);
8403 AssertComRCReturn (rc, rc);
8404 }
8405
8406 /* create a combined server-side progress object when necessary */
8407 ComObjPtr <CombinedProgress> combinedProgress;
8408 if (takingSnapshotOnline)
8409 {
8410 combinedProgress.createObject();
8411 rc = combinedProgress->init (mParent, aInitiator, progressDesc,
8412 serverProgress, aProgress);
8413 AssertComRCReturn (rc, rc);
8414 }
8415
8416 /* create a snapshot object */
8417 RTTIMESPEC time;
8418 ComObjPtr <Snapshot> snapshot;
8419 snapshot.createObject();
8420 rc = snapshot->init (snapshotId, aName, aDescription,
8421 RTTimeSpecGetMilli (RTTimeNow (&time)),
8422 snapshotMachine, mData->mCurrentSnapshot);
8423 AssertComRCReturn (rc, rc);
8424
8425 /*
8426 * create and start the task on a separate thread
8427 * (note that it will not start working until we release alock)
8428 */
8429 TakeSnapshotTask *task = new TakeSnapshotTask (this);
8430 int vrc = RTThreadCreate (NULL, taskHandler,
8431 (void *) task,
8432 0, RTTHREADTYPE_MAIN_WORKER, 0, "TakeSnapshot");
8433 if (VBOX_FAILURE (vrc))
8434 {
8435 snapshot->uninit();
8436 delete task;
8437 ComAssertFailedRet (E_FAIL);
8438 }
8439
8440 /* fill in the snapshot data */
8441 mSnapshotData.mLastState = mData->mMachineState;
8442 mSnapshotData.mSnapshot = snapshot;
8443 mSnapshotData.mServerProgress = serverProgress;
8444 mSnapshotData.mCombinedProgress = combinedProgress;
8445
8446 /* set the state to Saving (this is expected by Console::TakeSnapshot()) */
8447 setMachineState (MachineState_Saving);
8448
8449 if (takingSnapshotOnline)
8450 stateFilePath.cloneTo (aStateFilePath);
8451 else
8452 *aStateFilePath = NULL;
8453
8454 serverProgress.queryInterfaceTo (aServerProgress);
8455
8456 LogFlowThisFuncLeave();
8457 return S_OK;
8458}
8459
8460/**
8461 * @note Locks mParent + this objects for writing.
8462 */
8463STDMETHODIMP SessionMachine::EndTakingSnapshot (BOOL aSuccess)
8464{
8465 LogFlowThisFunc (("\n"));
8466
8467 AutoCaller autoCaller (this);
8468 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8469
8470 /* Lock mParent because of endTakingSnapshot() */
8471 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
8472
8473 AssertReturn (!aSuccess ||
8474 (mData->mMachineState == MachineState_Saving &&
8475 mSnapshotData.mLastState != MachineState_InvalidMachineState &&
8476 !mSnapshotData.mSnapshot.isNull() &&
8477 !mSnapshotData.mServerProgress.isNull() &&
8478 !mSnapshotData.mCombinedProgress.isNull()),
8479 E_FAIL);
8480
8481 /*
8482 * set the state to the state we had when BeginTakingSnapshot() was called
8483 * (this is expected by Console::TakeSnapshot() and
8484 * Console::saveStateThread())
8485 */
8486 setMachineState (mSnapshotData.mLastState);
8487
8488 return endTakingSnapshot (aSuccess);
8489}
8490
8491/**
8492 * @note Locks mParent + this + children objects for writing!
8493 */
8494STDMETHODIMP SessionMachine::DiscardSnapshot (
8495 IConsole *aInitiator, INPTR GUIDPARAM aId,
8496 MachineState_T *aMachineState, IProgress **aProgress)
8497{
8498 LogFlowThisFunc (("\n"));
8499
8500 Guid id = aId;
8501 AssertReturn (aInitiator && !id.isEmpty(), E_INVALIDARG);
8502 AssertReturn (aMachineState && aProgress, E_POINTER);
8503
8504 AutoCaller autoCaller (this);
8505 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8506
8507 /* Progress::init() needs mParent lock */
8508 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
8509
8510 ComAssertRet (mData->mMachineState < MachineState_Running, E_FAIL);
8511
8512 ComObjPtr <Snapshot> snapshot;
8513 HRESULT rc = findSnapshot (id, snapshot, true /* aSetError */);
8514 CheckComRCReturnRC (rc);
8515
8516 AutoLock snapshotLock (snapshot);
8517 if (snapshot == mData->mFirstSnapshot)
8518 {
8519 AutoLock chLock (mData->mFirstSnapshot->childrenLock());
8520 size_t childrenCount = mData->mFirstSnapshot->children().size();
8521 if (childrenCount > 1)
8522 return setError (E_FAIL,
8523 tr ("Cannot discard the snapshot '%ls' because it is the first "
8524 "snapshot of the machine '%ls' and it has more than one "
8525 "child snapshot (%d)"),
8526 snapshot->data().mName.raw(), mUserData->mName.raw(),
8527 childrenCount);
8528 }
8529
8530 /*
8531 * If the snapshot being discarded is the current one, ensure current
8532 * settings are committed and saved.
8533 */
8534 if (snapshot == mData->mCurrentSnapshot)
8535 {
8536 if (isModified())
8537 {
8538 rc = saveSettings();
8539 CheckComRCReturnRC (rc);
8540 }
8541 }
8542
8543 /*
8544 * create a progress object. The number of operations is:
8545 * 1 (preparing) + # of VDIs
8546 */
8547 ComObjPtr <Progress> progress;
8548 progress.createObject();
8549 rc = progress->init (mParent, aInitiator,
8550 Bstr (Utf8StrFmt (tr ("Discarding snapshot '%ls'"),
8551 snapshot->data().mName.raw())),
8552 FALSE /* aCancelable */,
8553 1 + snapshot->data().mMachine->mHDData->mHDAttachments.size(),
8554 Bstr (tr ("Preparing to discard snapshot")));
8555 AssertComRCReturn (rc, rc);
8556
8557 /* create and start the task on a separate thread */
8558 DiscardSnapshotTask *task = new DiscardSnapshotTask (this, progress, snapshot);
8559 int vrc = RTThreadCreate (NULL, taskHandler,
8560 (void *) task,
8561 0, RTTHREADTYPE_MAIN_WORKER, 0, "DiscardSnapshot");
8562 if (VBOX_FAILURE (vrc))
8563 delete task;
8564 ComAssertRCRet (vrc, E_FAIL);
8565
8566 /* set the proper machine state (note: after creating a Task instance) */
8567 setMachineState (MachineState_Discarding);
8568
8569 /* return the progress to the caller */
8570 progress.queryInterfaceTo (aProgress);
8571
8572 /* return the new state to the caller */
8573 *aMachineState = mData->mMachineState;
8574
8575 return S_OK;
8576}
8577
8578/**
8579 * @note Locks mParent + this + children objects for writing!
8580 */
8581STDMETHODIMP SessionMachine::DiscardCurrentState (
8582 IConsole *aInitiator, MachineState_T *aMachineState, IProgress **aProgress)
8583{
8584 LogFlowThisFunc (("\n"));
8585
8586 AssertReturn (aInitiator, E_INVALIDARG);
8587 AssertReturn (aMachineState && aProgress, E_POINTER);
8588
8589 AutoCaller autoCaller (this);
8590 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8591
8592 /* Progress::init() needs mParent lock */
8593 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
8594
8595 ComAssertRet (mData->mMachineState < MachineState_Running, E_FAIL);
8596
8597 if (mData->mCurrentSnapshot.isNull())
8598 return setError (E_FAIL,
8599 tr ("Could not discard the current state of the machine '%ls' "
8600 "because it doesn't have any snapshots"),
8601 mUserData->mName.raw());
8602
8603 /*
8604 * create a progress object. The number of operations is:
8605 * 1 (preparing) + # of VDIs + 1 (if we need to copy the saved state file)
8606 */
8607 ComObjPtr <Progress> progress;
8608 progress.createObject();
8609 {
8610 ULONG opCount = 1 + mData->mCurrentSnapshot->data()
8611 .mMachine->mHDData->mHDAttachments.size();
8612 if (mData->mCurrentSnapshot->stateFilePath())
8613 ++ opCount;
8614 progress->init (mParent, aInitiator,
8615 Bstr (tr ("Discarding current machine state")),
8616 FALSE /* aCancelable */, opCount,
8617 Bstr (tr ("Preparing to discard current state")));
8618 }
8619
8620 /* create and start the task on a separate thread */
8621 DiscardCurrentStateTask *task =
8622 new DiscardCurrentStateTask (this, progress, false /* discardCurSnapshot */);
8623 int vrc = RTThreadCreate (NULL, taskHandler,
8624 (void *) task,
8625 0, RTTHREADTYPE_MAIN_WORKER, 0, "DiscardCurState");
8626 if (VBOX_FAILURE (vrc))
8627 delete task;
8628 ComAssertRCRet (vrc, E_FAIL);
8629
8630 /* set the proper machine state (note: after creating a Task instance) */
8631 setMachineState (MachineState_Discarding);
8632
8633 /* return the progress to the caller */
8634 progress.queryInterfaceTo (aProgress);
8635
8636 /* return the new state to the caller */
8637 *aMachineState = mData->mMachineState;
8638
8639 return S_OK;
8640}
8641
8642/**
8643 * @note Locks mParent + other objects for writing!
8644 */
8645STDMETHODIMP SessionMachine::DiscardCurrentSnapshotAndState (
8646 IConsole *aInitiator, MachineState_T *aMachineState, IProgress **aProgress)
8647{
8648 LogFlowThisFunc (("\n"));
8649
8650 AssertReturn (aInitiator, E_INVALIDARG);
8651 AssertReturn (aMachineState && aProgress, E_POINTER);
8652
8653 AutoCaller autoCaller (this);
8654 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8655
8656 /* Progress::init() needs mParent lock */
8657 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
8658
8659 ComAssertRet (mData->mMachineState < MachineState_Running, E_FAIL);
8660
8661 if (mData->mCurrentSnapshot.isNull())
8662 return setError (E_FAIL,
8663 tr ("Could not discard the current state of the machine '%ls' "
8664 "because it doesn't have any snapshots"),
8665 mUserData->mName.raw());
8666
8667 /*
8668 * create a progress object. The number of operations is:
8669 * 1 (preparing) + # of VDIs in the current snapshot +
8670 * # of VDIs in the previous snapshot +
8671 * 1 (if we need to copy the saved state file of the previous snapshot)
8672 * or (if there is no previous snapshot):
8673 * 1 (preparing) + # of VDIs in the current snapshot * 2 +
8674 * 1 (if we need to copy the saved state file of the current snapshot)
8675 */
8676 ComObjPtr <Progress> progress;
8677 progress.createObject();
8678 {
8679 ComObjPtr <Snapshot> curSnapshot = mData->mCurrentSnapshot;
8680 ComObjPtr <Snapshot> prevSnapshot = mData->mCurrentSnapshot->parent();
8681
8682 ULONG opCount = 1;
8683 if (prevSnapshot)
8684 {
8685 opCount += curSnapshot->data().mMachine->mHDData->mHDAttachments.size();
8686 opCount += prevSnapshot->data().mMachine->mHDData->mHDAttachments.size();
8687 if (prevSnapshot->stateFilePath())
8688 ++ opCount;
8689 }
8690 else
8691 {
8692 opCount += curSnapshot->data().mMachine->mHDData->mHDAttachments.size() * 2;
8693 if (curSnapshot->stateFilePath())
8694 ++ opCount;
8695 }
8696
8697 progress->init (mParent, aInitiator,
8698 Bstr (tr ("Discarding current machine snapshot and state")),
8699 FALSE /* aCancelable */, opCount,
8700 Bstr (tr ("Preparing to discard current snapshot and state")));
8701 }
8702
8703 /* create and start the task on a separate thread */
8704 DiscardCurrentStateTask *task =
8705 new DiscardCurrentStateTask (this, progress, true /* discardCurSnapshot */);
8706 int vrc = RTThreadCreate (NULL, taskHandler,
8707 (void *) task,
8708 0, RTTHREADTYPE_MAIN_WORKER, 0, "DiscardCurState");
8709 if (VBOX_FAILURE (vrc))
8710 delete task;
8711 ComAssertRCRet (vrc, E_FAIL);
8712
8713 /* set the proper machine state (note: after creating a Task instance) */
8714 setMachineState (MachineState_Discarding);
8715
8716 /* return the progress to the caller */
8717 progress.queryInterfaceTo (aProgress);
8718
8719 /* return the new state to the caller */
8720 *aMachineState = mData->mMachineState;
8721
8722 return S_OK;
8723}
8724
8725// public methods only for internal purposes
8726/////////////////////////////////////////////////////////////////////////////
8727
8728/**
8729 * Called from the client watcher thread to check for unexpected client
8730 * process death.
8731 *
8732 * @note On Win32 and on OS/2, this method is called only when we've got the
8733 * mutex (i.e. the client has either died or terminated normally). This
8734 * method always returns true.
8735 *
8736 * @note On Linux, the method returns true if the client process has
8737 * terminated abnormally (and/or the session has been uninitialized) and
8738 * false if it is still alive.
8739 *
8740 * @note Locks this object for writing.
8741 */
8742bool SessionMachine::checkForDeath()
8743{
8744 Uninit::Reason reason;
8745 bool doUninit = false;
8746 bool ret = false;
8747
8748 /*
8749 * Enclose autoCaller with a block because calling uninit()
8750 * from under it will deadlock.
8751 */
8752 {
8753 AutoCaller autoCaller (this);
8754 if (!autoCaller.isOk())
8755 {
8756 /*
8757 * return true if not ready, to cause the client watcher to exclude
8758 * the corresponding session from watching
8759 */
8760 LogFlowThisFunc (("Already uninitialized!"));
8761 return true;
8762 }
8763
8764 AutoLock alock (this);
8765
8766 /*
8767 * Determine the reason of death: if the session state is Closing here,
8768 * everything is fine. Otherwise it means that the client did not call
8769 * OnSessionEnd() before it released the IPC semaphore.
8770 * This may happen either because the client process has abnormally
8771 * terminated, or because it simply forgot to call ISession::Close()
8772 * before exiting. We threat the latter also as an abnormal termination
8773 * (see Session::uninit() for details).
8774 */
8775 reason = mData->mSession.mState == SessionState_SessionClosing ?
8776 Uninit::Normal :
8777 Uninit::Abnormal;
8778
8779#if defined(RT_OS_WINDOWS)
8780
8781 AssertMsg (mIPCSem, ("semaphore must be created"));
8782
8783 /* release the IPC mutex */
8784 ::ReleaseMutex (mIPCSem);
8785
8786 doUninit = true;
8787
8788 ret = true;
8789
8790#elif defined(RT_OS_OS2)
8791
8792 AssertMsg (mIPCSem, ("semaphore must be created"));
8793
8794 /* release the IPC mutex */
8795 ::DosReleaseMutexSem (mIPCSem);
8796
8797 doUninit = true;
8798
8799 ret = true;
8800
8801#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
8802
8803 AssertMsg (mIPCSem >= 0, ("semaphore must be created"));
8804
8805 int val = ::semctl (mIPCSem, 0, GETVAL);
8806 if (val > 0)
8807 {
8808 /* the semaphore is signaled, meaning the session is terminated */
8809 doUninit = true;
8810 }
8811
8812 ret = val > 0;
8813
8814#else
8815# error "Port me!"
8816#endif
8817
8818 } /* AutoCaller block */
8819
8820 if (doUninit)
8821 uninit (reason);
8822
8823 return ret;
8824}
8825
8826/**
8827 * @note Locks this object for reading.
8828 */
8829HRESULT SessionMachine::onDVDDriveChange()
8830{
8831 LogFlowThisFunc (("\n"));
8832
8833 AutoCaller autoCaller (this);
8834 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8835
8836 ComPtr <IInternalSessionControl> directControl;
8837 {
8838 AutoReaderLock alock (this);
8839 directControl = mData->mSession.mDirectControl;
8840 }
8841
8842 /* ignore notifications sent after #OnSessionEnd() is called */
8843 if (!directControl)
8844 return S_OK;
8845
8846 return directControl->OnDVDDriveChange();
8847}
8848
8849/**
8850 * @note Locks this object for reading.
8851 */
8852HRESULT SessionMachine::onFloppyDriveChange()
8853{
8854 LogFlowThisFunc (("\n"));
8855
8856 AutoCaller autoCaller (this);
8857 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8858
8859 ComPtr <IInternalSessionControl> directControl;
8860 {
8861 AutoReaderLock alock (this);
8862 directControl = mData->mSession.mDirectControl;
8863 }
8864
8865 /* ignore notifications sent after #OnSessionEnd() is called */
8866 if (!directControl)
8867 return S_OK;
8868
8869 return directControl->OnFloppyDriveChange();
8870}
8871
8872/**
8873 * @note Locks this object for reading.
8874 */
8875HRESULT SessionMachine::onNetworkAdapterChange(INetworkAdapter *networkAdapter)
8876{
8877 LogFlowThisFunc (("\n"));
8878
8879 AutoCaller autoCaller (this);
8880 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8881
8882 ComPtr <IInternalSessionControl> directControl;
8883 {
8884 AutoReaderLock alock (this);
8885 directControl = mData->mSession.mDirectControl;
8886 }
8887
8888 /* ignore notifications sent after #OnSessionEnd() is called */
8889 if (!directControl)
8890 return S_OK;
8891
8892 return directControl->OnNetworkAdapterChange(networkAdapter);
8893}
8894
8895/**
8896 * @note Locks this object for reading.
8897 */
8898HRESULT SessionMachine::onSerialPortChange(ISerialPort *serialPort)
8899{
8900 LogFlowThisFunc (("\n"));
8901
8902 AutoCaller autoCaller (this);
8903 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8904
8905 ComPtr <IInternalSessionControl> directControl;
8906 {
8907 AutoReaderLock alock (this);
8908 directControl = mData->mSession.mDirectControl;
8909 }
8910
8911 /* ignore notifications sent after #OnSessionEnd() is called */
8912 if (!directControl)
8913 return S_OK;
8914
8915 return directControl->OnSerialPortChange(serialPort);
8916}
8917
8918/**
8919 * @note Locks this object for reading.
8920 */
8921HRESULT SessionMachine::onParallelPortChange(IParallelPort *parallelPort)
8922{
8923 LogFlowThisFunc (("\n"));
8924
8925 AutoCaller autoCaller (this);
8926 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8927
8928 ComPtr <IInternalSessionControl> directControl;
8929 {
8930 AutoReaderLock alock (this);
8931 directControl = mData->mSession.mDirectControl;
8932 }
8933
8934 /* ignore notifications sent after #OnSessionEnd() is called */
8935 if (!directControl)
8936 return S_OK;
8937
8938 return directControl->OnParallelPortChange(parallelPort);
8939}
8940
8941/**
8942 * @note Locks this object for reading.
8943 */
8944HRESULT SessionMachine::onVRDPServerChange()
8945{
8946 LogFlowThisFunc (("\n"));
8947
8948 AutoCaller autoCaller (this);
8949 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8950
8951 ComPtr <IInternalSessionControl> directControl;
8952 {
8953 AutoReaderLock alock (this);
8954 directControl = mData->mSession.mDirectControl;
8955 }
8956
8957 /* ignore notifications sent after #OnSessionEnd() is called */
8958 if (!directControl)
8959 return S_OK;
8960
8961 return directControl->OnVRDPServerChange();
8962}
8963
8964/**
8965 * @note Locks this object for reading.
8966 */
8967HRESULT SessionMachine::onUSBControllerChange()
8968{
8969 LogFlowThisFunc (("\n"));
8970
8971 AutoCaller autoCaller (this);
8972 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8973
8974 ComPtr <IInternalSessionControl> directControl;
8975 {
8976 AutoReaderLock alock (this);
8977 directControl = mData->mSession.mDirectControl;
8978 }
8979
8980 /* ignore notifications sent after #OnSessionEnd() is called */
8981 if (!directControl)
8982 return S_OK;
8983
8984 return directControl->OnUSBControllerChange();
8985}
8986
8987/**
8988 * @note Locks this object for reading.
8989 */
8990HRESULT SessionMachine::onSharedFolderChange()
8991{
8992 LogFlowThisFunc (("\n"));
8993
8994 AutoCaller autoCaller (this);
8995 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8996
8997 ComPtr <IInternalSessionControl> directControl;
8998 {
8999 AutoReaderLock alock (this);
9000 directControl = mData->mSession.mDirectControl;
9001 }
9002
9003 /* ignore notifications sent after #OnSessionEnd() is called */
9004 if (!directControl)
9005 return S_OK;
9006
9007 return directControl->OnSharedFolderChange (FALSE /* aGlobal */);
9008}
9009
9010/**
9011 * @note Locks this object for reading.
9012 */
9013HRESULT SessionMachine::onUSBDeviceAttach (IUSBDevice *aDevice,
9014 IVirtualBoxErrorInfo *aError)
9015{
9016 LogFlowThisFunc (("\n"));
9017
9018 AutoCaller autoCaller (this);
9019
9020 /* This notification may happen after the machine object has been
9021 * uninitialized (the session was closed), so don't assert. */
9022 CheckComRCReturnRC (autoCaller.rc());
9023
9024 ComPtr <IInternalSessionControl> directControl;
9025 {
9026 AutoReaderLock alock (this);
9027 directControl = mData->mSession.mDirectControl;
9028 }
9029
9030 /* fail on notifications sent after #OnSessionEnd() is called, it is
9031 * expected by the caller */
9032 if (!directControl)
9033 return E_FAIL;
9034
9035 return directControl->OnUSBDeviceAttach (aDevice, aError);
9036}
9037
9038/**
9039 * @note Locks this object for reading.
9040 */
9041HRESULT SessionMachine::onUSBDeviceDetach (INPTR GUIDPARAM aId,
9042 IVirtualBoxErrorInfo *aError)
9043{
9044 LogFlowThisFunc (("\n"));
9045
9046 AutoCaller autoCaller (this);
9047
9048 /* This notification may happen after the machine object has been
9049 * uninitialized (the session was closed), so don't assert. */
9050 CheckComRCReturnRC (autoCaller.rc());
9051
9052 ComPtr <IInternalSessionControl> directControl;
9053 {
9054 AutoReaderLock alock (this);
9055 directControl = mData->mSession.mDirectControl;
9056 }
9057
9058 /* fail on notifications sent after #OnSessionEnd() is called, it is
9059 * expected by the caller */
9060 if (!directControl)
9061 return E_FAIL;
9062
9063 return directControl->OnUSBDeviceDetach (aId, aError);
9064}
9065
9066// protected methods
9067/////////////////////////////////////////////////////////////////////////////
9068
9069/**
9070 * Helper method to finalize saving the state.
9071 *
9072 * @note Must be called from under this object's lock.
9073 *
9074 * @param aSuccess TRUE if the snapshot has been taken successfully
9075 *
9076 * @note Locks mParent + this objects for writing.
9077 */
9078HRESULT SessionMachine::endSavingState (BOOL aSuccess)
9079{
9080 LogFlowThisFuncEnter();
9081
9082 AutoCaller autoCaller (this);
9083 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
9084
9085 /* mParent->removeProgress() needs mParent lock */
9086 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
9087
9088 HRESULT rc = S_OK;
9089
9090 if (aSuccess)
9091 {
9092 mSSData->mStateFilePath = mSnapshotData.mStateFilePath;
9093
9094 /* save all VM settings */
9095 rc = saveSettings();
9096 }
9097 else
9098 {
9099 /* delete the saved state file (it might have been already created) */
9100 RTFileDelete (Utf8Str (mSnapshotData.mStateFilePath));
9101 }
9102
9103 /* remove the completed progress object */
9104 mParent->removeProgress (mSnapshotData.mProgressId);
9105
9106 /* clear out the temporary saved state data */
9107 mSnapshotData.mLastState = MachineState_InvalidMachineState;
9108 mSnapshotData.mProgressId.clear();
9109 mSnapshotData.mStateFilePath.setNull();
9110
9111 LogFlowThisFuncLeave();
9112 return rc;
9113}
9114
9115/**
9116 * Helper method to finalize taking a snapshot.
9117 * Gets called only from #EndTakingSnapshot() that is expected to
9118 * be called by the VM process when it finishes *all* the tasks related to
9119 * taking a snapshot, either scucessfully or unsuccessfilly.
9120 *
9121 * @param aSuccess TRUE if the snapshot has been taken successfully
9122 *
9123 * @note Locks mParent + this objects for writing.
9124 */
9125HRESULT SessionMachine::endTakingSnapshot (BOOL aSuccess)
9126{
9127 LogFlowThisFuncEnter();
9128
9129 AutoCaller autoCaller (this);
9130 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
9131
9132 /* Progress object uninitialization needs mParent lock */
9133 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
9134
9135 HRESULT rc = S_OK;
9136
9137 if (aSuccess)
9138 {
9139 /* the server progress must be completed on success */
9140 Assert (mSnapshotData.mServerProgress->completed());
9141
9142 mData->mCurrentSnapshot = mSnapshotData.mSnapshot;
9143 /* memorize the first snapshot if necessary */
9144 if (!mData->mFirstSnapshot)
9145 mData->mFirstSnapshot = mData->mCurrentSnapshot;
9146
9147 int opFlags = SaveSS_AddOp | SaveSS_UpdateCurrentId;
9148 if (mSnapshotData.mLastState != MachineState_Paused && !isModified())
9149 {
9150 /*
9151 * the machine was powered off or saved when taking a snapshot,
9152 * so reset the mCurrentStateModified flag
9153 */
9154 mData->mCurrentStateModified = FALSE;
9155 opFlags |= SaveSS_UpdateCurStateModified;
9156 }
9157
9158 rc = saveSnapshotSettings (mSnapshotData.mSnapshot, opFlags);
9159 }
9160
9161 if (!aSuccess || FAILED (rc))
9162 {
9163 if (mSnapshotData.mSnapshot)
9164 {
9165 /* wait for the completion of the server progress (diff VDI creation) */
9166 /// @todo (dmik) later, we will definitely want to cancel it instead
9167 // (when the cancel function is implemented)
9168 mSnapshotData.mServerProgress->WaitForCompletion (-1);
9169
9170 /*
9171 * delete all differencing VDIs created
9172 * (this will attach their parents back)
9173 */
9174 rc = deleteSnapshotDiffs (mSnapshotData.mSnapshot);
9175 /* continue cleanup on error */
9176
9177 /* delete the saved state file (it might have been already created) */
9178 if (mSnapshotData.mSnapshot->stateFilePath())
9179 RTFileDelete (Utf8Str (mSnapshotData.mSnapshot->stateFilePath()));
9180
9181 mSnapshotData.mSnapshot->uninit();
9182 }
9183 }
9184
9185 /* inform callbacks */
9186 if (aSuccess && SUCCEEDED (rc))
9187 mParent->onSnapshotTaken (mData->mUuid, mSnapshotData.mSnapshot->data().mId);
9188
9189 /* clear out the snapshot data */
9190 mSnapshotData.mLastState = MachineState_InvalidMachineState;
9191 mSnapshotData.mSnapshot.setNull();
9192 mSnapshotData.mServerProgress.setNull();
9193 /* uninitialize the combined progress (to remove it from the VBox collection) */
9194 if (!mSnapshotData.mCombinedProgress.isNull())
9195 {
9196 mSnapshotData.mCombinedProgress->uninit();
9197 mSnapshotData.mCombinedProgress.setNull();
9198 }
9199
9200 LogFlowThisFuncLeave();
9201 return rc;
9202}
9203
9204/**
9205 * Take snapshot task handler.
9206 * Must be called only by TakeSnapshotTask::handler()!
9207 *
9208 * The sole purpose of this task is to asynchronously create differencing VDIs
9209 * and copy the saved state file (when necessary). The VM process will wait
9210 * for this task to complete using the mSnapshotData.mServerProgress
9211 * returned to it.
9212 *
9213 * @note Locks mParent + this objects for writing.
9214 */
9215void SessionMachine::takeSnapshotHandler (TakeSnapshotTask &aTask)
9216{
9217 LogFlowThisFuncEnter();
9218
9219 AutoCaller autoCaller (this);
9220
9221 LogFlowThisFunc (("state=%d\n", autoCaller.state()));
9222 if (!autoCaller.isOk())
9223 {
9224 /*
9225 * we might have been uninitialized because the session was
9226 * accidentally closed by the client, so don't assert
9227 */
9228 LogFlowThisFuncLeave();
9229 return;
9230 }
9231
9232 /* endTakingSnapshot() needs mParent lock */
9233 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
9234
9235 HRESULT rc = S_OK;
9236
9237 LogFlowThisFunc (("Creating differencing VDIs...\n"));
9238
9239 /* create new differencing hard disks and attach them to this machine */
9240 rc = createSnapshotDiffs (&mSnapshotData.mSnapshot->data().mId,
9241 mUserData->mSnapshotFolderFull,
9242 mSnapshotData.mServerProgress,
9243 true /* aOnline */);
9244
9245 if (SUCCEEDED (rc) && mSnapshotData.mLastState == MachineState_Saved)
9246 {
9247 Utf8Str stateFrom = mSSData->mStateFilePath;
9248 Utf8Str stateTo = mSnapshotData.mSnapshot->stateFilePath();
9249
9250 LogFlowThisFunc (("Copying the execution state from '%s' to '%s'...\n",
9251 stateFrom.raw(), stateTo.raw()));
9252
9253 mSnapshotData.mServerProgress->advanceOperation (
9254 Bstr (tr ("Copying the execution state")));
9255
9256 /*
9257 * We can safely leave the lock here:
9258 * mMachineState is MachineState_Saving here
9259 */
9260 alock.leave();
9261
9262 /* copy the state file */
9263 int vrc = RTFileCopyEx (stateFrom, stateTo, progressCallback,
9264 static_cast <Progress *> (mSnapshotData.mServerProgress));
9265
9266 alock.enter();
9267
9268 if (VBOX_FAILURE (vrc))
9269 rc = setError (E_FAIL,
9270 tr ("Could not copy the state file '%ls' to '%ls' (%Vrc)"),
9271 stateFrom.raw(), stateTo.raw());
9272 }
9273
9274 /*
9275 * we have to call endTakingSnapshot() here if the snapshot was taken
9276 * offline, because the VM process will not do it in this case
9277 */
9278 if (mSnapshotData.mLastState != MachineState_Paused)
9279 {
9280 LogFlowThisFunc (("Finalizing the taken snapshot (rc=%08X)...\n", rc));
9281
9282 setMachineState (mSnapshotData.mLastState);
9283 updateMachineStateOnClient();
9284
9285 /* finalize the progress after setting the state, for consistency */
9286 mSnapshotData.mServerProgress->notifyComplete (rc);
9287
9288 endTakingSnapshot (SUCCEEDED (rc));
9289 }
9290 else
9291 {
9292 mSnapshotData.mServerProgress->notifyComplete (rc);
9293 }
9294
9295 LogFlowThisFuncLeave();
9296}
9297
9298/**
9299 * Discard snapshot task handler.
9300 * Must be called only by DiscardSnapshotTask::handler()!
9301 *
9302 * When aTask.subTask is true, the associated progress object is left
9303 * uncompleted on success. On failure, the progress is marked as completed
9304 * regardless of this parameter.
9305 *
9306 * @note Locks mParent + this + child objects for writing!
9307 */
9308void SessionMachine::discardSnapshotHandler (DiscardSnapshotTask &aTask)
9309{
9310 LogFlowThisFuncEnter();
9311
9312 AutoCaller autoCaller (this);
9313
9314 LogFlowThisFunc (("state=%d\n", autoCaller.state()));
9315 if (!autoCaller.isOk())
9316 {
9317 /*
9318 * we might have been uninitialized because the session was
9319 * accidentally closed by the client, so don't assert
9320 */
9321 aTask.progress->notifyComplete (
9322 E_FAIL, COM_IIDOF (IMachine), getComponentName(),
9323 tr ("The session has been accidentally closed"));
9324
9325 LogFlowThisFuncLeave();
9326 return;
9327 }
9328
9329 ComObjPtr <SnapshotMachine> sm = aTask.snapshot->data().mMachine;
9330
9331 /* mParent is locked because of Progress::notifyComplete(), etc. */
9332 AutoMultiLock <3> alock (mParent->wlock(), this->wlock(), sm->rlock());
9333
9334 /* Safe locking in the direction parent->child */
9335 AutoLock snapshotLock (aTask.snapshot);
9336 AutoLock snapshotChildrenLock (aTask.snapshot->childrenLock());
9337
9338 HRESULT rc = S_OK;
9339
9340 /* save the snapshot ID (for callbacks) */
9341 Guid snapshotId = aTask.snapshot->data().mId;
9342
9343 do
9344 {
9345 /* first pass: */
9346 LogFlowThisFunc (("Check hard disk accessibility and affected machines...\n"));
9347
9348 HDData::HDAttachmentList::const_iterator it;
9349 for (it = sm->mHDData->mHDAttachments.begin();
9350 it != sm->mHDData->mHDAttachments.end();
9351 ++ it)
9352 {
9353 ComObjPtr <HardDiskAttachment> hda = *it;
9354 ComObjPtr <HardDisk> hd = hda->hardDisk();
9355 ComObjPtr <HardDisk> parent = hd->parent();
9356
9357 AutoLock hdLock (hd);
9358
9359 if (hd->hasForeignChildren())
9360 {
9361 rc = setError (E_FAIL,
9362 tr ("One or more hard disks belonging to other machines are "
9363 "based on the hard disk '%ls' stored in the snapshot '%ls'"),
9364 hd->toString().raw(), aTask.snapshot->data().mName.raw());
9365 break;
9366 }
9367
9368 if (hd->type() == HardDiskType_NormalHardDisk)
9369 {
9370 AutoLock hdChildrenLock (hd->childrenLock());
9371 size_t childrenCount = hd->children().size();
9372 if (childrenCount > 1)
9373 {
9374 rc = setError (E_FAIL,
9375 tr ("Normal hard disk '%ls' stored in the snapshot '%ls' "
9376 "has more than one child hard disk (%d)"),
9377 hd->toString().raw(), aTask.snapshot->data().mName.raw(),
9378 childrenCount);
9379 break;
9380 }
9381 }
9382 else
9383 {
9384 ComAssertMsgFailedBreak (("Invalid hard disk type %d\n", hd->type()),
9385 rc = E_FAIL);
9386 }
9387
9388 Bstr accessError;
9389 rc = hd->getAccessibleWithChildren (accessError);
9390 CheckComRCBreakRC (rc);
9391
9392 if (!accessError.isNull())
9393 {
9394 rc = setError (E_FAIL,
9395 tr ("Hard disk '%ls' stored in the snapshot '%ls' is not "
9396 "accessible (%ls)"),
9397 hd->toString().raw(), aTask.snapshot->data().mName.raw(),
9398 accessError.raw());
9399 break;
9400 }
9401
9402 rc = hd->setBusyWithChildren();
9403 if (FAILED (rc))
9404 {
9405 /* reset the busy flag of all previous hard disks */
9406 while (it != sm->mHDData->mHDAttachments.begin())
9407 (*(-- it))->hardDisk()->clearBusyWithChildren();
9408 break;
9409 }
9410 }
9411
9412 CheckComRCBreakRC (rc);
9413
9414 /* second pass: */
9415 LogFlowThisFunc (("Performing actual vdi merging...\n"));
9416
9417 for (it = sm->mHDData->mHDAttachments.begin();
9418 it != sm->mHDData->mHDAttachments.end();
9419 ++ it)
9420 {
9421 ComObjPtr <HardDiskAttachment> hda = *it;
9422 ComObjPtr <HardDisk> hd = hda->hardDisk();
9423 ComObjPtr <HardDisk> parent = hd->parent();
9424
9425 AutoLock hdLock (hd);
9426
9427 Bstr hdRootString = hd->root()->toString (true /* aShort */);
9428
9429 if (parent)
9430 {
9431 if (hd->isParentImmutable())
9432 {
9433 aTask.progress->advanceOperation (Bstr (Utf8StrFmt (
9434 tr ("Discarding changes to immutable hard disk '%ls'"),
9435 hdRootString.raw())));
9436
9437 /* clear the busy flag before unregistering */
9438 hd->clearBusy();
9439
9440 /*
9441 * unregisterDiffHardDisk() is supposed to delete and uninit
9442 * the differencing hard disk
9443 */
9444 rc = mParent->unregisterDiffHardDisk (hd);
9445 CheckComRCBreakRC (rc);
9446 continue;
9447 }
9448 else
9449 {
9450 /*
9451 * differencing VDI:
9452 * merge this image to all its children
9453 */
9454
9455 aTask.progress->advanceOperation (Bstr (Utf8StrFmt (
9456 tr ("Merging changes to normal hard disk '%ls' to children"),
9457 hdRootString.raw())));
9458
9459 snapshotChildrenLock.unlock();
9460 snapshotLock.unlock();
9461 alock.leave();
9462
9463 rc = hd->asVDI()->mergeImageToChildren (aTask.progress);
9464
9465 alock.enter();
9466 snapshotLock.lock();
9467 snapshotChildrenLock.lock();
9468
9469 // debug code
9470 // if (it != sm->mHDData->mHDAttachments.begin())
9471 // {
9472 // rc = setError (E_FAIL, "Simulated failure");
9473 // break;
9474 //}
9475
9476 if (SUCCEEDED (rc))
9477 rc = mParent->unregisterDiffHardDisk (hd);
9478 else
9479 hd->clearBusyWithChildren();
9480
9481 CheckComRCBreakRC (rc);
9482 }
9483 }
9484 else if (hd->type() == HardDiskType_NormalHardDisk)
9485 {
9486 /*
9487 * normal vdi has the only child or none
9488 * (checked in the first pass)
9489 */
9490
9491 ComObjPtr <HardDisk> child;
9492 {
9493 AutoLock hdChildrenLock (hd->childrenLock());
9494 if (hd->children().size())
9495 child = hd->children().front();
9496 }
9497
9498 if (child.isNull())
9499 {
9500 aTask.progress->advanceOperation (Bstr (Utf8StrFmt (
9501 tr ("Detaching normal hard disk '%ls'"),
9502 hdRootString.raw())));
9503
9504 /* just deassociate the normal image from this machine */
9505 hd->setMachineId (Guid());
9506 hd->setSnapshotId (Guid());
9507
9508 /* clear the busy flag */
9509 hd->clearBusy();
9510 }
9511 else
9512 {
9513 AutoLock childLock (child);
9514
9515 aTask.progress->advanceOperation (Bstr (Utf8StrFmt (
9516 tr ("Preserving changes to normal hard disk '%ls'"),
9517 hdRootString.raw())));
9518
9519 ComObjPtr <Machine> cm;
9520 ComObjPtr <Snapshot> cs;
9521 ComObjPtr <HardDiskAttachment> childHda;
9522 rc = findHardDiskAttachment (child, &cm, &cs, &childHda);
9523 CheckComRCBreakRC (rc);
9524 /* must be the same machine (checked in the first pass) */
9525 ComAssertBreak (cm->mData->mUuid == mData->mUuid, rc = E_FAIL);
9526
9527 /* merge the child to this basic image */
9528
9529 snapshotChildrenLock.unlock();
9530 snapshotLock.unlock();
9531 alock.leave();
9532
9533 rc = child->asVDI()->mergeImageToParent (aTask.progress);
9534
9535 alock.enter();
9536 snapshotLock.lock();
9537 snapshotChildrenLock.lock();
9538
9539 if (SUCCEEDED (rc))
9540 rc = mParent->unregisterDiffHardDisk (child);
9541 else
9542 hd->clearBusyWithChildren();
9543
9544 CheckComRCBreakRC (rc);
9545
9546 /* reset the snapshot Id */
9547 hd->setSnapshotId (Guid());
9548
9549 /* replace the child image in the appropriate place */
9550 childHda->updateHardDisk (hd, FALSE /* aDirty */);
9551
9552 if (!cs)
9553 {
9554 aTask.settingsChanged = true;
9555 }
9556 else
9557 {
9558 rc = cm->saveSnapshotSettings (cs, SaveSS_UpdateAllOp);
9559 CheckComRCBreakRC (rc);
9560 }
9561 }
9562 }
9563 else
9564 {
9565 ComAssertMsgFailedBreak (("Invalid hard disk type %d\n", hd->type()),
9566 rc = E_FAIL);
9567 }
9568 }
9569
9570 /* preserve existing error info */
9571 ErrorInfoKeeper mergeEik;
9572 HRESULT mergeRc = rc;
9573
9574 if (FAILED (rc))
9575 {
9576 /* clear the busy flag on the rest of hard disks */
9577 for (++ it; it != sm->mHDData->mHDAttachments.end(); ++ it)
9578 (*it)->hardDisk()->clearBusyWithChildren();
9579 }
9580
9581 /*
9582 * we have to try to discard the snapshot even if merging failed
9583 * because some images might have been already merged (and deleted)
9584 */
9585
9586 do
9587 {
9588 LogFlowThisFunc (("Discarding the snapshot (reparenting children)...\n"));
9589
9590 ComObjPtr <Snapshot> parentSnapshot = aTask.snapshot->parent();
9591
9592 /// @todo (dmik):
9593 // when we introduce clones later, discarding the snapshot
9594 // will affect the current and first snapshots of clones, if they are
9595 // direct children of this snapshot. So we will need to lock machines
9596 // associated with child snapshots as well and update mCurrentSnapshot
9597 // and/or mFirstSnapshot fields.
9598
9599 if (aTask.snapshot == mData->mCurrentSnapshot)
9600 {
9601 /* currently, the parent snapshot must refer to the same machine */
9602 ComAssertBreak (
9603 !parentSnapshot ||
9604 parentSnapshot->data().mMachine->mData->mUuid == mData->mUuid,
9605 rc = E_FAIL);
9606 mData->mCurrentSnapshot = parentSnapshot;
9607 /* mark the current state as modified */
9608 mData->mCurrentStateModified = TRUE;
9609 }
9610
9611 if (aTask.snapshot == mData->mFirstSnapshot)
9612 {
9613 /*
9614 * the first snapshot must have only one child when discarded,
9615 * or no children at all
9616 */
9617 ComAssertBreak (aTask.snapshot->children().size() <= 1, rc = E_FAIL);
9618
9619 if (aTask.snapshot->children().size() == 1)
9620 {
9621 ComObjPtr <Snapshot> childSnapshot = aTask.snapshot->children().front();
9622 ComAssertBreak (
9623 childSnapshot->data().mMachine->mData->mUuid == mData->mUuid,
9624 rc = E_FAIL);
9625 mData->mFirstSnapshot = childSnapshot;
9626 }
9627 else
9628 mData->mFirstSnapshot.setNull();
9629 }
9630
9631 /// @todo (dmik)
9632 // if we implement some warning mechanism later, we'll have
9633 // to return a warning if the state file path cannot be deleted
9634 Bstr stateFilePath = aTask.snapshot->stateFilePath();
9635 if (stateFilePath)
9636 RTFileDelete (Utf8Str (stateFilePath));
9637
9638 aTask.snapshot->discard();
9639
9640 rc = saveSnapshotSettings (parentSnapshot,
9641 SaveSS_UpdateAllOp | SaveSS_UpdateCurrentId);
9642 }
9643 while (0);
9644
9645 /* restore the merge error if any (ErrorInfo will be restored
9646 * automatically) */
9647 if (FAILED (mergeRc))
9648 rc = mergeRc;
9649 }
9650 while (0);
9651
9652 if (!aTask.subTask || FAILED (rc))
9653 {
9654 if (!aTask.subTask)
9655 {
9656 /* preserve existing error info */
9657 ErrorInfoKeeper eik;
9658
9659 /* restore the machine state */
9660 setMachineState (aTask.state);
9661 updateMachineStateOnClient();
9662
9663 /*
9664 * save settings anyway, since we've already changed the current
9665 * machine configuration
9666 */
9667 if (aTask.settingsChanged)
9668 {
9669 saveSettings (true /* aMarkCurStateAsModified */,
9670 true /* aInformCallbacksAnyway */);
9671 }
9672 }
9673
9674 /* set the result (this will try to fetch current error info on failure) */
9675 aTask.progress->notifyComplete (rc);
9676 }
9677
9678 if (SUCCEEDED (rc))
9679 mParent->onSnapshotDiscarded (mData->mUuid, snapshotId);
9680
9681 LogFlowThisFunc (("Done discarding snapshot (rc=%08X)\n", rc));
9682 LogFlowThisFuncLeave();
9683}
9684
9685/**
9686 * Discard current state task handler.
9687 * Must be called only by DiscardCurrentStateTask::handler()!
9688 *
9689 * @note Locks mParent + this object for writing.
9690 */
9691void SessionMachine::discardCurrentStateHandler (DiscardCurrentStateTask &aTask)
9692{
9693 LogFlowThisFuncEnter();
9694
9695 AutoCaller autoCaller (this);
9696
9697 LogFlowThisFunc (("state=%d\n", autoCaller.state()));
9698 if (!autoCaller.isOk())
9699 {
9700 /*
9701 * we might have been uninitialized because the session was
9702 * accidentally closed by the client, so don't assert
9703 */
9704 aTask.progress->notifyComplete (
9705 E_FAIL, COM_IIDOF (IMachine), getComponentName(),
9706 tr ("The session has been accidentally closed"));
9707
9708 LogFlowThisFuncLeave();
9709 return;
9710 }
9711
9712 /* mParent is locked because of Progress::notifyComplete(), etc. */
9713 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
9714
9715 /*
9716 * discard all current changes to mUserData (name, OSType etc.)
9717 * (note that the machine is powered off, so there is no need
9718 * to inform the direct session)
9719 */
9720 if (isModified())
9721 rollback (false /* aNotify */);
9722
9723 HRESULT rc = S_OK;
9724
9725 bool errorInSubtask = false;
9726 bool stateRestored = false;
9727
9728 const bool isLastSnapshot = mData->mCurrentSnapshot->parent().isNull();
9729
9730 do
9731 {
9732 /*
9733 * discard the saved state file if the machine was Saved prior
9734 * to this operation
9735 */
9736 if (aTask.state == MachineState_Saved)
9737 {
9738 Assert (!mSSData->mStateFilePath.isEmpty());
9739 RTFileDelete (Utf8Str (mSSData->mStateFilePath));
9740 mSSData->mStateFilePath.setNull();
9741 aTask.modifyLastState (MachineState_PoweredOff);
9742 rc = saveStateSettings (SaveSTS_StateFilePath);
9743 CheckComRCBreakRC (rc);
9744 }
9745
9746 if (aTask.discardCurrentSnapshot && !isLastSnapshot)
9747 {
9748 /*
9749 * the "discard current snapshot and state" task is in action,
9750 * the current snapshot is not the last one.
9751 * Discard the current snapshot first.
9752 */
9753
9754 DiscardSnapshotTask subTask (aTask, mData->mCurrentSnapshot);
9755 subTask.subTask = true;
9756 discardSnapshotHandler (subTask);
9757 aTask.settingsChanged = subTask.settingsChanged;
9758 if (aTask.progress->completed())
9759 {
9760 /*
9761 * the progress can be completed by a subtask only if there was
9762 * a failure
9763 */
9764 Assert (FAILED (aTask.progress->resultCode()));
9765 errorInSubtask = true;
9766 rc = aTask.progress->resultCode();
9767 break;
9768 }
9769 }
9770
9771 LONG64 snapshotTimeStamp = 0;
9772
9773 {
9774 ComObjPtr <Snapshot> curSnapshot = mData->mCurrentSnapshot;
9775 AutoLock snapshotLock (curSnapshot);
9776
9777 /* remember the timestamp of the snapshot we're restoring from */
9778 snapshotTimeStamp = curSnapshot->data().mTimeStamp;
9779
9780 /* copy all hardware data from the current snapshot */
9781 copyFrom (curSnapshot->data().mMachine);
9782
9783 LogFlowThisFunc (("Restoring VDIs from the snapshot...\n"));
9784
9785 /* restore the attachmends from the snapshot */
9786 mHDData.backup();
9787 mHDData->mHDAttachments =
9788 curSnapshot->data().mMachine->mHDData->mHDAttachments;
9789
9790 snapshotLock.unlock();
9791 alock.leave();
9792 rc = createSnapshotDiffs (NULL, mUserData->mSnapshotFolderFull,
9793 aTask.progress,
9794 false /* aOnline */);
9795 alock.enter();
9796 snapshotLock.lock();
9797
9798 if (FAILED (rc))
9799 {
9800 /* here we can still safely rollback, so do it */
9801 /* preserve existing error info */
9802 ErrorInfoKeeper eik;
9803 /* undo all changes */
9804 rollback (false /* aNotify */);
9805 break;
9806 }
9807
9808 /*
9809 * note: old VDIs will be deassociated/deleted on #commit() called
9810 * either from #saveSettings() or directly at the end
9811 */
9812
9813 /* should not have a saved state file associated at this point */
9814 Assert (mSSData->mStateFilePath.isNull());
9815
9816 if (curSnapshot->stateFilePath())
9817 {
9818 Utf8Str snapStateFilePath = curSnapshot->stateFilePath();
9819
9820 Utf8Str stateFilePath = Utf8StrFmt ("%ls%c{%Vuuid}.sav",
9821 mUserData->mSnapshotFolderFull.raw(),
9822 RTPATH_DELIMITER, mData->mUuid.raw());
9823
9824 LogFlowThisFunc (("Copying saved state file from '%s' to '%s'...\n",
9825 snapStateFilePath.raw(), stateFilePath.raw()));
9826
9827 aTask.progress->advanceOperation (
9828 Bstr (tr ("Restoring the execution state")));
9829
9830 /* copy the state file */
9831 snapshotLock.unlock();
9832 alock.leave();
9833 int vrc = RTFileCopyEx (snapStateFilePath, stateFilePath,
9834 progressCallback, aTask.progress);
9835 alock.enter();
9836 snapshotLock.lock();
9837
9838 if (VBOX_SUCCESS (vrc))
9839 {
9840 mSSData->mStateFilePath = stateFilePath;
9841 }
9842 else
9843 {
9844 rc = setError (E_FAIL,
9845 tr ("Could not copy the state file '%s' to '%s' (%Vrc)"),
9846 snapStateFilePath.raw(), stateFilePath.raw(), vrc);
9847 break;
9848 }
9849 }
9850 }
9851
9852 bool informCallbacks = false;
9853
9854 if (aTask.discardCurrentSnapshot && isLastSnapshot)
9855 {
9856 /*
9857 * discard the current snapshot and state task is in action,
9858 * the current snapshot is the last one.
9859 * Discard the current snapshot after discarding the current state.
9860 */
9861
9862 /* commit changes to fixup hard disks before discarding */
9863 rc = commit();
9864 if (SUCCEEDED (rc))
9865 {
9866 DiscardSnapshotTask subTask (aTask, mData->mCurrentSnapshot);
9867 subTask.subTask = true;
9868 discardSnapshotHandler (subTask);
9869 aTask.settingsChanged = subTask.settingsChanged;
9870 if (aTask.progress->completed())
9871 {
9872 /*
9873 * the progress can be completed by a subtask only if there
9874 * was a failure
9875 */
9876 Assert (FAILED (aTask.progress->resultCode()));
9877 errorInSubtask = true;
9878 rc = aTask.progress->resultCode();
9879 }
9880 }
9881
9882 /*
9883 * we've committed already, so inform callbacks anyway to ensure
9884 * they don't miss some change
9885 */
9886 informCallbacks = true;
9887 }
9888
9889 /*
9890 * we have already discarded the current state, so set the
9891 * execution state accordingly no matter of the discard snapshot result
9892 */
9893 if (mSSData->mStateFilePath)
9894 setMachineState (MachineState_Saved);
9895 else
9896 setMachineState (MachineState_PoweredOff);
9897
9898 updateMachineStateOnClient();
9899 stateRestored = true;
9900
9901 if (errorInSubtask)
9902 break;
9903
9904 /* assign the timestamp from the snapshot */
9905 Assert (snapshotTimeStamp != 0);
9906 mData->mLastStateChange = snapshotTimeStamp;
9907
9908 /* mark the current state as not modified */
9909 mData->mCurrentStateModified = FALSE;
9910
9911 /* save all settings and commit */
9912 rc = saveSettings (false /* aMarkCurStateAsModified */,
9913 informCallbacks);
9914 aTask.settingsChanged = false;
9915 }
9916 while (0);
9917
9918 if (FAILED (rc))
9919 {
9920 /* preserve existing error info */
9921 ErrorInfoKeeper eik;
9922
9923 if (!stateRestored)
9924 {
9925 /* restore the machine state */
9926 setMachineState (aTask.state);
9927 updateMachineStateOnClient();
9928 }
9929
9930 /*
9931 * save all settings and commit if still modified (there is no way to
9932 * rollback properly). Note that isModified() will return true after
9933 * copyFrom(). Also save the settings if requested by the subtask.
9934 */
9935 if (isModified() || aTask.settingsChanged)
9936 {
9937 if (aTask.settingsChanged)
9938 saveSettings (true /* aMarkCurStateAsModified */,
9939 true /* aInformCallbacksAnyway */);
9940 else
9941 saveSettings();
9942 }
9943 }
9944
9945 if (!errorInSubtask)
9946 {
9947 /* set the result (this will try to fetch current error info on failure) */
9948 aTask.progress->notifyComplete (rc);
9949 }
9950
9951 if (SUCCEEDED (rc))
9952 mParent->onSnapshotDiscarded (mData->mUuid, Guid());
9953
9954 LogFlowThisFunc (("Done discarding current state (rc=%08X)\n", rc));
9955
9956 LogFlowThisFuncLeave();
9957}
9958
9959/**
9960 * Helper to change the machine state (reimplementation).
9961 *
9962 * @note Locks this object for writing.
9963 */
9964HRESULT SessionMachine::setMachineState (MachineState_T aMachineState)
9965{
9966 LogFlowThisFuncEnter();
9967 LogFlowThisFunc (("aMachineState=%d\n", aMachineState));
9968
9969 AutoCaller autoCaller (this);
9970 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
9971
9972 AutoLock alock (this);
9973
9974 MachineState_T oldMachineState = mData->mMachineState;
9975
9976 AssertMsgReturn (oldMachineState != aMachineState,
9977 ("oldMachineState=%d, aMachineState=%d\n",
9978 oldMachineState, aMachineState), E_FAIL);
9979
9980 HRESULT rc = S_OK;
9981
9982 int stsFlags = 0;
9983 bool deleteSavedState = false;
9984
9985 /* detect some state transitions */
9986
9987 if (oldMachineState < MachineState_Running &&
9988 aMachineState >= MachineState_Running &&
9989 aMachineState != MachineState_Discarding)
9990 {
9991 /*
9992 * the EMT thread is about to start, so mark attached HDDs as busy
9993 * and all its ancestors as being in use
9994 */
9995 for (HDData::HDAttachmentList::const_iterator it =
9996 mHDData->mHDAttachments.begin();
9997 it != mHDData->mHDAttachments.end();
9998 ++ it)
9999 {
10000 ComObjPtr <HardDisk> hd = (*it)->hardDisk();
10001 AutoLock hdLock (hd);
10002 hd->setBusy();
10003 hd->addReaderOnAncestors();
10004 }
10005 }
10006 else
10007 if (oldMachineState >= MachineState_Running &&
10008 oldMachineState != MachineState_Discarding &&
10009 aMachineState < MachineState_Running)
10010 {
10011 /*
10012 * the EMT thread stopped, so mark attached HDDs as no more busy
10013 * and remove the in-use flag from all its ancestors
10014 */
10015 for (HDData::HDAttachmentList::const_iterator it =
10016 mHDData->mHDAttachments.begin();
10017 it != mHDData->mHDAttachments.end();
10018 ++ it)
10019 {
10020 ComObjPtr <HardDisk> hd = (*it)->hardDisk();
10021 AutoLock hdLock (hd);
10022 hd->releaseReaderOnAncestors();
10023 hd->clearBusy();
10024 }
10025 }
10026
10027 if (oldMachineState == MachineState_Restoring)
10028 {
10029 if (aMachineState != MachineState_Saved)
10030 {
10031 /*
10032 * delete the saved state file once the machine has finished
10033 * restoring from it (note that Console sets the state from
10034 * Restoring to Saved if the VM couldn't restore successfully,
10035 * to give the user an ability to fix an error and retry --
10036 * we keep the saved state file in this case)
10037 */
10038 deleteSavedState = true;
10039 }
10040 }
10041 else
10042 if (oldMachineState == MachineState_Saved &&
10043 (aMachineState == MachineState_PoweredOff ||
10044 aMachineState == MachineState_Aborted))
10045 {
10046 /*
10047 * delete the saved state after Console::DiscardSavedState() is called
10048 * or if the VM process (owning a direct VM session) crashed while the
10049 * VM was Saved
10050 */
10051
10052 /// @todo (dmik)
10053 // Not sure that deleting the saved state file just because of the
10054 // client death before it attempted to restore the VM is a good
10055 // thing. But when it crashes we need to go to the Aborted state
10056 // which cannot have the saved state file associated... The only
10057 // way to fix this is to make the Aborted condition not a VM state
10058 // but a bool flag: i.e., when a crash occurs, set it to true and
10059 // change the state to PoweredOff or Saved depending on the
10060 // saved state presence.
10061
10062 deleteSavedState = true;
10063 mData->mCurrentStateModified = TRUE;
10064 stsFlags |= SaveSTS_CurStateModified;
10065 }
10066
10067 if (aMachineState == MachineState_Starting ||
10068 aMachineState == MachineState_Restoring)
10069 {
10070 /*
10071 * set the current state modified flag to indicate that the
10072 * current state is no more identical to the state in the
10073 * current snapshot
10074 */
10075 if (!mData->mCurrentSnapshot.isNull())
10076 {
10077 mData->mCurrentStateModified = TRUE;
10078 stsFlags |= SaveSTS_CurStateModified;
10079 }
10080 }
10081
10082 if (deleteSavedState == true)
10083 {
10084 Assert (!mSSData->mStateFilePath.isEmpty());
10085 RTFileDelete (Utf8Str (mSSData->mStateFilePath));
10086 mSSData->mStateFilePath.setNull();
10087 stsFlags |= SaveSTS_StateFilePath;
10088 }
10089
10090 /* redirect to the underlying peer machine */
10091 mPeer->setMachineState (aMachineState);
10092
10093 if (aMachineState == MachineState_PoweredOff ||
10094 aMachineState == MachineState_Aborted ||
10095 aMachineState == MachineState_Saved)
10096 {
10097 stsFlags |= SaveSTS_StateTimeStamp;
10098 }
10099
10100 rc = saveStateSettings (stsFlags);
10101
10102 if ((oldMachineState != MachineState_PoweredOff &&
10103 oldMachineState != MachineState_Aborted) &&
10104 (aMachineState == MachineState_PoweredOff ||
10105 aMachineState == MachineState_Aborted))
10106 {
10107 /*
10108 * clear differencing hard disks based on immutable hard disks
10109 * once we've been shut down for any reason
10110 */
10111 rc = wipeOutImmutableDiffs();
10112 }
10113
10114 LogFlowThisFunc (("rc=%08X\n", rc));
10115 LogFlowThisFuncLeave();
10116 return rc;
10117}
10118
10119/**
10120 * Sends the current machine state value to the VM process.
10121 *
10122 * @note Locks this object for reading, then calls a client process.
10123 */
10124HRESULT SessionMachine::updateMachineStateOnClient()
10125{
10126 AutoCaller autoCaller (this);
10127 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
10128
10129 ComPtr <IInternalSessionControl> directControl;
10130 {
10131 AutoReaderLock alock (this);
10132 AssertReturn (!!mData, E_FAIL);
10133 directControl = mData->mSession.mDirectControl;
10134
10135 /* directControl may be already set to NULL here in #OnSessionEnd()
10136 * called too early by the direct session process while there is still
10137 * some operation (like discarding the snapshot) in progress. The client
10138 * process in this case is waiting inside Session::close() for the
10139 * "end session" process object to complete, while #uninit() called by
10140 * #checkForDeath() on the Watcher thread is waiting for the pending
10141 * operation to complete. For now, we accept this inconsitent behavior
10142 * and simply do nothing here. */
10143
10144 if (mData->mSession.mState == SessionState_SessionClosing)
10145 return S_OK;
10146
10147 AssertReturn (!directControl.isNull(), E_FAIL);
10148 }
10149
10150 return directControl->UpdateMachineState (mData->mMachineState);
10151}
10152
10153/* static */
10154DECLCALLBACK(int) SessionMachine::taskHandler (RTTHREAD thread, void *pvUser)
10155{
10156 AssertReturn (pvUser, VERR_INVALID_POINTER);
10157
10158 Task *task = static_cast <Task *> (pvUser);
10159 task->handler();
10160
10161 // it's our responsibility to delete the task
10162 delete task;
10163
10164 return 0;
10165}
10166
10167/////////////////////////////////////////////////////////////////////////////
10168// SnapshotMachine class
10169/////////////////////////////////////////////////////////////////////////////
10170
10171DEFINE_EMPTY_CTOR_DTOR (SnapshotMachine)
10172
10173HRESULT SnapshotMachine::FinalConstruct()
10174{
10175 LogFlowThisFunc (("\n"));
10176
10177 /* set the proper type to indicate we're the SnapshotMachine instance */
10178 unconst (mType) = IsSnapshotMachine;
10179
10180 return S_OK;
10181}
10182
10183void SnapshotMachine::FinalRelease()
10184{
10185 LogFlowThisFunc (("\n"));
10186
10187 uninit();
10188}
10189
10190/**
10191 * Initializes the SnapshotMachine object when taking a snapshot.
10192 *
10193 * @param aSessionMachine machine to take a snapshot from
10194 * @param aSnapshotId snapshot ID of this snapshot machine
10195 * @param aStateFilePath file where the execution state will be later saved
10196 * (or NULL for the offline snapshot)
10197 *
10198 * @note Locks aSessionMachine object for reading.
10199 */
10200HRESULT SnapshotMachine::init (SessionMachine *aSessionMachine,
10201 INPTR GUIDPARAM aSnapshotId,
10202 INPTR BSTR aStateFilePath)
10203{
10204 LogFlowThisFuncEnter();
10205 LogFlowThisFunc (("mName={%ls}\n", aSessionMachine->mUserData->mName.raw()));
10206
10207 AssertReturn (aSessionMachine && !Guid (aSnapshotId).isEmpty(), E_INVALIDARG);
10208
10209 /* Enclose the state transition NotReady->InInit->Ready */
10210 AutoInitSpan autoInitSpan (this);
10211 AssertReturn (autoInitSpan.isOk(), E_UNEXPECTED);
10212
10213 mSnapshotId = aSnapshotId;
10214
10215 AutoReaderLock alock (aSessionMachine);
10216
10217 /* memorize the primary Machine instance (i.e. not SessionMachine!) */
10218 unconst (mPeer) = aSessionMachine->mPeer;
10219 /* share the parent pointer */
10220 unconst (mParent) = mPeer->mParent;
10221
10222 /* take the pointer to Data to share */
10223 mData.share (mPeer->mData);
10224 /*
10225 * take the pointer to UserData to share
10226 * (our UserData must always be the same as Machine's data)
10227 */
10228 mUserData.share (mPeer->mUserData);
10229 /* make a private copy of all other data (recent changes from SessionMachine) */
10230 mHWData.attachCopy (aSessionMachine->mHWData);
10231 mHDData.attachCopy (aSessionMachine->mHDData);
10232
10233 /* SSData is always unique for SnapshotMachine */
10234 mSSData.allocate();
10235 mSSData->mStateFilePath = aStateFilePath;
10236
10237 /*
10238 * create copies of all shared folders (mHWData after attiching a copy
10239 * contains just references to original objects)
10240 */
10241 for (HWData::SharedFolderList::iterator it = mHWData->mSharedFolders.begin();
10242 it != mHWData->mSharedFolders.end();
10243 ++ it)
10244 {
10245 ComObjPtr <SharedFolder> folder;
10246 folder.createObject();
10247 HRESULT rc = folder->initCopy (this, *it);
10248 CheckComRCReturnRC (rc);
10249 *it = folder;
10250 }
10251
10252 /* create all other child objects that will be immutable private copies */
10253
10254 unconst (mBIOSSettings).createObject();
10255 mBIOSSettings->initCopy (this, mPeer->mBIOSSettings);
10256
10257#ifdef VBOX_VRDP
10258 unconst (mVRDPServer).createObject();
10259 mVRDPServer->initCopy (this, mPeer->mVRDPServer);
10260#endif
10261
10262 unconst (mDVDDrive).createObject();
10263 mDVDDrive->initCopy (this, mPeer->mDVDDrive);
10264
10265 unconst (mFloppyDrive).createObject();
10266 mFloppyDrive->initCopy (this, mPeer->mFloppyDrive);
10267
10268 unconst (mAudioAdapter).createObject();
10269 mAudioAdapter->initCopy (this, mPeer->mAudioAdapter);
10270
10271 unconst (mUSBController).createObject();
10272 mUSBController->initCopy (this, mPeer->mUSBController);
10273
10274 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
10275 {
10276 unconst (mNetworkAdapters [slot]).createObject();
10277 mNetworkAdapters [slot]->initCopy (this, mPeer->mNetworkAdapters [slot]);
10278 }
10279
10280 for (ULONG slot = 0; slot < ELEMENTS (mSerialPorts); slot ++)
10281 {
10282 unconst (mSerialPorts [slot]).createObject();
10283 mSerialPorts [slot]->initCopy (this, mPeer->mSerialPorts [slot]);
10284 }
10285
10286 for (ULONG slot = 0; slot < ELEMENTS (mParallelPorts); slot ++)
10287 {
10288 unconst (mParallelPorts [slot]).createObject();
10289 mParallelPorts [slot]->initCopy (this, mPeer->mParallelPorts [slot]);
10290 }
10291
10292 /* Confirm a successful initialization when it's the case */
10293 autoInitSpan.setSucceeded();
10294
10295 LogFlowThisFuncLeave();
10296 return S_OK;
10297}
10298
10299/**
10300 * Initializes the SnapshotMachine object when loading from the settings file.
10301 *
10302 * @param aMachine machine the snapshot belngs to
10303 * @param aHWNode <Hardware> node
10304 * @param aHDAsNode <HardDiskAttachments> node
10305 * @param aSnapshotId snapshot ID of this snapshot machine
10306 * @param aStateFilePath file where the execution state is saved
10307 * (or NULL for the offline snapshot)
10308 *
10309 * @note Locks aMachine object for reading.
10310 */
10311HRESULT SnapshotMachine::init (Machine *aMachine, CFGNODE aHWNode, CFGNODE aHDAsNode,
10312 INPTR GUIDPARAM aSnapshotId, INPTR BSTR aStateFilePath)
10313{
10314 LogFlowThisFuncEnter();
10315 LogFlowThisFunc (("mName={%ls}\n", aMachine->mUserData->mName.raw()));
10316
10317 AssertReturn (aMachine && aHWNode && aHDAsNode && !Guid (aSnapshotId).isEmpty(),
10318 E_INVALIDARG);
10319
10320 /* Enclose the state transition NotReady->InInit->Ready */
10321 AutoInitSpan autoInitSpan (this);
10322 AssertReturn (autoInitSpan.isOk(), E_UNEXPECTED);
10323
10324 mSnapshotId = aSnapshotId;
10325
10326 AutoReaderLock alock (aMachine);
10327
10328 /* memorize the primary Machine instance */
10329 unconst (mPeer) = aMachine;
10330 /* share the parent pointer */
10331 unconst (mParent) = mPeer->mParent;
10332
10333 /* take the pointer to Data to share */
10334 mData.share (mPeer->mData);
10335 /*
10336 * take the pointer to UserData to share
10337 * (our UserData must always be the same as Machine's data)
10338 */
10339 mUserData.share (mPeer->mUserData);
10340 /* allocate private copies of all other data (will be loaded from settings) */
10341 mHWData.allocate();
10342 mHDData.allocate();
10343
10344 /* SSData is always unique for SnapshotMachine */
10345 mSSData.allocate();
10346 mSSData->mStateFilePath = aStateFilePath;
10347
10348 /* create all other child objects that will be immutable private copies */
10349
10350 unconst (mBIOSSettings).createObject();
10351 mBIOSSettings->init (this);
10352
10353#ifdef VBOX_VRDP
10354 unconst (mVRDPServer).createObject();
10355 mVRDPServer->init (this);
10356#endif
10357
10358 unconst (mDVDDrive).createObject();
10359 mDVDDrive->init (this);
10360
10361 unconst (mFloppyDrive).createObject();
10362 mFloppyDrive->init (this);
10363
10364 unconst (mAudioAdapter).createObject();
10365 mAudioAdapter->init (this);
10366
10367 unconst (mUSBController).createObject();
10368 mUSBController->init (this);
10369
10370 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
10371 {
10372 unconst (mNetworkAdapters [slot]).createObject();
10373 mNetworkAdapters [slot]->init (this, slot);
10374 }
10375
10376 for (ULONG slot = 0; slot < ELEMENTS (mSerialPorts); slot ++)
10377 {
10378 unconst (mSerialPorts [slot]).createObject();
10379 mSerialPorts [slot]->init (this, slot);
10380 }
10381
10382 for (ULONG slot = 0; slot < ELEMENTS (mParallelPorts); slot ++)
10383 {
10384 unconst (mParallelPorts [slot]).createObject();
10385 mParallelPorts [slot]->init (this, slot);
10386 }
10387
10388 /* load hardware and harddisk settings */
10389
10390 HRESULT rc = loadHardware (aHWNode);
10391 if (SUCCEEDED (rc))
10392 rc = loadHardDisks (aHDAsNode, true /* aRegistered */, &mSnapshotId);
10393
10394 if (SUCCEEDED (rc))
10395 {
10396 /* commit all changes made during the initialization */
10397 commit();
10398 }
10399
10400 /* Confirm a successful initialization when it's the case */
10401 if (SUCCEEDED (rc))
10402 autoInitSpan.setSucceeded();
10403
10404 LogFlowThisFuncLeave();
10405 return rc;
10406}
10407
10408/**
10409 * Uninitializes this SnapshotMachine object.
10410 */
10411void SnapshotMachine::uninit()
10412{
10413 LogFlowThisFuncEnter();
10414
10415 /* Enclose the state transition Ready->InUninit->NotReady */
10416 AutoUninitSpan autoUninitSpan (this);
10417 if (autoUninitSpan.uninitDone())
10418 return;
10419
10420 uninitDataAndChildObjects();
10421
10422 unconst (mParent).setNull();
10423 unconst (mPeer).setNull();
10424
10425 LogFlowThisFuncLeave();
10426}
10427
10428// AutoLock::Lockable interface
10429////////////////////////////////////////////////////////////////////////////////
10430
10431/**
10432 * Overrides VirtualBoxBase::lockHandle() in order to share the lock handle
10433 * with the primary Machine instance (mPeer).
10434 */
10435AutoLock::Handle *SnapshotMachine::lockHandle() const
10436{
10437 AssertReturn (!mPeer.isNull(), NULL);
10438 return mPeer->lockHandle();
10439}
10440
10441// public methods only for internal purposes
10442////////////////////////////////////////////////////////////////////////////////
10443
10444/**
10445 * Called by the snapshot object associated with this SnapshotMachine when
10446 * snapshot data such as name or description is changed.
10447 *
10448 * @note Locks this object for writing.
10449 */
10450HRESULT SnapshotMachine::onSnapshotChange (Snapshot *aSnapshot)
10451{
10452 AutoLock alock (this);
10453
10454 mPeer->saveSnapshotSettings (aSnapshot, SaveSS_UpdateAttrsOp);
10455
10456 /* inform callbacks */
10457 mParent->onSnapshotChange (mData->mUuid, aSnapshot->data().mId);
10458
10459 return S_OK;
10460}
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