VirtualBox

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

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

Added PXEDebug option scaffolding.

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