VirtualBox

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

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

Broke snapshots

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