VirtualBox

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

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

Biggest check-in ever. New source code headers for all (C) innotek files.

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