VirtualBox

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

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

Main/Frontends: Pass the value of the DISPLAY variable of the starting application to the VM process (#2101).

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