VirtualBox

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

Last change on this file since 3942 was 3861, checked in by vboxsync, 17 years ago

use generic functions for determining paths instead of home-brewn solutions

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