VirtualBox

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

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

Main: Added new IMachine attributes: description, sessionType and sessionPid.

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

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