VirtualBox

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

Last change on this file since 4514 was 4513, checked in by vboxsync, 18 years ago

Reapplied fixed 24093.

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

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