VirtualBox

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

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

Main: Fixed: A saved state file for a newly created machine was created in the {uuid} dir instead of 'Snapshots'.

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