VirtualBox

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

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

Updated the new command line syntax

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 307.1 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.215389.xyz. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License as published by the Free Software Foundation,
13 * in version 2 as it comes in the "COPYING" file of the VirtualBox OSE
14 * distribution. VirtualBox OSE is distributed in the hope that it will
15 * be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * 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#ifdef __WIN__
2639 const char * args[] = {path, "-startvm", idStr, 0 };
2640#else
2641 Utf8Str name = mUserData->mName;
2642 const char * args[] = {path, "-comment", name, "-startvm", idStr, 0 };
2643#endif
2644 vrc = RTProcCreate (path, args, NULL, 0, &pid);
2645 }
2646 else
2647#ifdef VBOX_VRDP
2648 if (type == "vrdp")
2649 {
2650 const char VBoxVRDP_exe[] = "VBoxVRDP" HOSTSUFF_EXE;
2651 Assert (sz >= sizeof (VBoxVRDP_exe));
2652 strcpy (cmd, VBoxVRDP_exe);
2653
2654 Utf8Str idStr = mData->mUuid.toString();
2655#ifdef __WIN__
2656 const char * args[] = {path, "-startvm", idStr, 0 };
2657#else
2658 Utf8Str name = mUserData->mName;
2659 const char * args[] = {path, "-comment", name, "-startvm", idStr, 0 };
2660#endif
2661 vrc = RTProcCreate (path, args, NULL, 0, &pid);
2662 }
2663 else
2664#endif /* VBOX_VRDP */
2665 if (type == "capture")
2666 {
2667 const char VBoxVRDP_exe[] = "VBoxVRDP" HOSTSUFF_EXE;
2668 Assert (sz >= sizeof (VBoxVRDP_exe));
2669 strcpy (cmd, VBoxVRDP_exe);
2670
2671 Utf8Str idStr = mData->mUuid.toString();
2672#ifdef __WIN__
2673 const char * args[] = {path, "-startvm", idStr, "-capture", 0 };
2674#else
2675 Utf8Str name = mUserData->mName;
2676 const char * args[] = {path, "-comment", name, "-startvm", idStr, "-capture", 0 };
2677#endif
2678 vrc = RTProcCreate (path, args, NULL, 0, &pid);
2679 }
2680 else
2681 {
2682 return setError (E_INVALIDARG,
2683 tr ("Invalid session type: '%ls'"), aType);
2684 }
2685
2686 if (VBOX_FAILURE (vrc))
2687 return setError (E_FAIL,
2688 tr ("Could not launch a process for the machine '%ls' (%Vrc)"),
2689 mUserData->mName.raw(), vrc);
2690
2691 LogFlowThisFunc (("launched.pid=%d(0x%x)\n", pid, pid));
2692
2693 /*
2694 * Note that we don't leave the lock here before calling the client,
2695 * because it doesn't need to call us back if called with a NULL argument.
2696 * Leaving the lock herer is dangerous because we didn't prepare the
2697 * launch data yet, but the client we've just started may happen to be
2698 * too fast and call openSession() that will fail (because of PID, etc.),
2699 * so that the Machine will never get out of the Spawning session state.
2700 */
2701
2702 /* inform the session that it will be a remote one */
2703 LogFlowThisFunc (("Calling AssignMachine (NULL)...\n"));
2704 HRESULT rc = aControl->AssignMachine (NULL);
2705 LogFlowThisFunc (("AssignMachine (NULL) returned %08X\n", rc));
2706
2707 if (FAILED (rc))
2708 {
2709 /* restore the session state */
2710 mData->mSession.mState = SessionState_SessionClosed;
2711 /* The failure may w/o any error info (from RPC), so provide one */
2712 return setError (rc,
2713 tr ("Failed to assign the machine to the session"));
2714 }
2715
2716 /* attach launch data to the machine */
2717 Assert (mData->mSession.mPid == NIL_RTPROCESS);
2718 mData->mSession.mRemoteControls.push_back (aControl);
2719 mData->mSession.mProgress = aProgress;
2720 mData->mSession.mPid = pid;
2721 mData->mSession.mState = SessionState_SessionSpawning;
2722 mData->mSession.mType = type;
2723
2724 LogFlowThisFuncLeave();
2725 return S_OK;
2726}
2727
2728/**
2729 * @note Locks this object for writing, calls the client process
2730 * (outside the lock).
2731 */
2732HRESULT Machine::openExistingSession (IInternalSessionControl *aControl)
2733{
2734 LogFlowThisFuncEnter();
2735
2736 AssertReturn (aControl, E_FAIL);
2737
2738 AutoCaller autoCaller (this);
2739 CheckComRCReturnRC (autoCaller.rc());
2740
2741 AutoLock alock (this);
2742
2743 if (!mData->mRegistered)
2744 return setError (E_UNEXPECTED,
2745 tr ("The machine '%ls' is not registered"), mUserData->mName.raw());
2746
2747 LogFlowThisFunc (("mSession.state=%d\n", mData->mSession.mState));
2748
2749 if (mData->mSession.mState != SessionState_SessionOpen)
2750 return setError (E_ACCESSDENIED,
2751 tr ("The machine '%ls' does not have an open session"),
2752 mUserData->mName.raw());
2753
2754 ComAssertRet (!mData->mSession.mDirectControl.isNull(), E_FAIL);
2755
2756 /*
2757 * Get the console from the direct session (note that we don't leave the
2758 * lock here because GetRemoteConsole must not call us back).
2759 */
2760 ComPtr <IConsole> console;
2761 HRESULT rc = mData->mSession.mDirectControl->
2762 GetRemoteConsole (console.asOutParam());
2763 if (FAILED (rc))
2764 {
2765 /* The failure may w/o any error info (from RPC), so provide one */
2766 return setError (rc,
2767 tr ("Failed to get a console object from the direct session"));
2768 }
2769
2770 ComAssertRet (!console.isNull(), E_FAIL);
2771
2772 ComObjPtr <SessionMachine> sessionMachine = mData->mSession.mMachine;
2773 AssertReturn (!sessionMachine.isNull(), E_FAIL);
2774
2775 /*
2776 * Leave the lock before calling the client process. It's safe here
2777 * since the only thing to do after we get the lock again is to add
2778 * the remote control to the list (which doesn't directly influence
2779 * anything).
2780 */
2781 alock.leave();
2782
2783 /* attach the remote session to the machine */
2784 LogFlowThisFunc (("Calling AssignRemoteMachine()...\n"));
2785 rc = aControl->AssignRemoteMachine (sessionMachine, console);
2786 LogFlowThisFunc (("AssignRemoteMachine() returned %08X\n", rc));
2787
2788 /* The failure may w/o any error info (from RPC), so provide one */
2789 if (FAILED (rc))
2790 return setError (rc,
2791 tr ("Failed to assign the machine to the session"));
2792
2793 alock.enter();
2794
2795 /* need to revalidate the state after entering the lock again */
2796 if (mData->mSession.mState != SessionState_SessionOpen)
2797 {
2798 aControl->Uninitialize();
2799
2800 return setError (E_ACCESSDENIED,
2801 tr ("The machine '%ls' does not have an open session"),
2802 mUserData->mName.raw());
2803 }
2804
2805 /* store the control in the list */
2806 mData->mSession.mRemoteControls.push_back (aControl);
2807
2808 LogFlowThisFuncLeave();
2809 return S_OK;
2810}
2811
2812/**
2813 * Checks that the registered flag of the machine can be set according to
2814 * the argument and sets it. On success, commits and saves all settings.
2815 *
2816 * @note When this machine is inaccessible, the only valid value for \a
2817 * aRegistered is FALSE (i.e. unregister the machine) because unregistered
2818 * inaccessible machines are not currently supported. Note that unregistering
2819 * an inaccessible machine will \b uninitialize this machine object. Therefore,
2820 * the caller must make sure there are no active Machine::addCaller() calls
2821 * on the current thread because this will block Machine::uninit().
2822 *
2823 * @note Locks this object and children for writing!
2824 */
2825HRESULT Machine::trySetRegistered (BOOL aRegistered)
2826{
2827 AutoLimitedCaller autoCaller (this);
2828 AssertComRCReturnRC (autoCaller.rc());
2829
2830 AutoLock alock (this);
2831
2832 ComAssertRet (mData->mRegistered != aRegistered, E_FAIL);
2833
2834 if (!mData->mAccessible)
2835 {
2836 /* A special case: the machine is not accessible. */
2837
2838 /* inaccessible machines can only be unregistered */
2839 AssertReturn (!aRegistered, E_FAIL);
2840
2841 /* Uninitialize ourselves here because currently there may be no
2842 * unregistered that are inaccessible (this state combination is not
2843 * supported). Note releasing the caller and leaving the lock before
2844 * calling uninit() */
2845
2846 alock.leave();
2847 autoCaller.release();
2848
2849 uninit();
2850
2851 return S_OK;
2852 }
2853
2854 AssertReturn (autoCaller.state() == Ready, E_FAIL);
2855
2856 if (aRegistered)
2857 {
2858 if (mData->mRegistered)
2859 return setError (E_FAIL,
2860 tr ("The machine '%ls' with UUID {%s} is already registered"),
2861 mUserData->mName.raw(),
2862 mData->mUuid.toString().raw());
2863 }
2864 else
2865 {
2866 if (mData->mMachineState == MachineState_Saved)
2867 return setError (E_FAIL,
2868 tr ("Cannot unregister the machine '%ls' because it "
2869 "is in the Saved state"),
2870 mUserData->mName.raw());
2871
2872 size_t snapshotCount = 0;
2873 if (mData->mFirstSnapshot)
2874 snapshotCount = mData->mFirstSnapshot->descendantCount() + 1;
2875 if (snapshotCount)
2876 return setError (E_FAIL,
2877 tr ("Cannot unregister the machine '%ls' because it "
2878 "has %d snapshots"),
2879 mUserData->mName.raw(), snapshotCount);
2880
2881 if (mData->mSession.mState != SessionState_SessionClosed)
2882 return setError (E_FAIL,
2883 tr ("Cannot unregister the machine '%ls' because it has an "
2884 "open session"),
2885 mUserData->mName.raw());
2886
2887 if (mHDData->mHDAttachments.size() != 0)
2888 return setError (E_FAIL,
2889 tr ("Cannot unregister the machine '%ls' because it "
2890 "has %d hard disks attached"),
2891 mUserData->mName.raw(), mHDData->mHDAttachments.size());
2892 }
2893
2894 /* Ensure the settings are saved. If we are going to be registered and
2895 * isConfigLocked() is FALSE then it means that no config file exists yet,
2896 * so create it. */
2897 if (isModified() || (aRegistered && !isConfigLocked()))
2898 {
2899 HRESULT rc = saveSettings();
2900 CheckComRCReturnRC (rc);
2901 }
2902
2903 mData->mRegistered = aRegistered;
2904
2905 /* inform the USB proxy about all attached/detached USB filters */
2906 mUSBController->onMachineRegistered (aRegistered);
2907
2908 return S_OK;
2909}
2910
2911// protected methods
2912/////////////////////////////////////////////////////////////////////////////
2913
2914/**
2915 * Helper to uninitialize all associated child objects
2916 * and to free all data structures.
2917 *
2918 * This method must be called as a part of the object's uninitialization
2919 * procedure (usually done in the uninit() method).
2920 *
2921 * @note Must be called only from uninit().
2922 */
2923void Machine::uninitDataAndChildObjects()
2924{
2925 AutoCaller autoCaller (this);
2926 AssertComRCReturn (autoCaller.rc(), (void) 0);
2927 AssertComRCReturn (autoCaller.state( ) == InUninit, (void) 0);
2928
2929 /* tell all our child objects we've been uninitialized */
2930
2931 /*
2932 * uninit all children using addDependentChild()/removeDependentChild()
2933 * in their init()/uninit() methods
2934 */
2935 uninitDependentChildren();
2936
2937 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
2938 {
2939 if (mNetworkAdapters [slot])
2940 {
2941 mNetworkAdapters [slot]->uninit();
2942 unconst (mNetworkAdapters [slot]).setNull();
2943 }
2944 }
2945
2946 if (mUSBController)
2947 {
2948 mUSBController->uninit();
2949 unconst (mUSBController).setNull();
2950 }
2951
2952 if (mAudioAdapter)
2953 {
2954 mAudioAdapter->uninit();
2955 unconst (mAudioAdapter).setNull();
2956 }
2957
2958 if (mFloppyDrive)
2959 {
2960 mFloppyDrive->uninit();
2961 unconst (mFloppyDrive).setNull();
2962 }
2963
2964 if (mDVDDrive)
2965 {
2966 mDVDDrive->uninit();
2967 unconst (mDVDDrive).setNull();
2968 }
2969
2970#ifdef VBOX_VRDP
2971 if (mVRDPServer)
2972 {
2973 mVRDPServer->uninit();
2974 unconst (mVRDPServer).setNull();
2975 }
2976#endif
2977
2978 if (mBIOSSettings)
2979 {
2980 mBIOSSettings->uninit();
2981 unconst (mBIOSSettings).setNull();
2982 }
2983
2984 /* free data structures */
2985 mSSData.free();
2986 mHDData.free();
2987 mHWData.free();
2988 mUserData.free();
2989 mData.free();
2990}
2991
2992/**
2993 * Helper to change the machine state.
2994 *
2995 * @note Locks this object for writing.
2996 */
2997HRESULT Machine::setMachineState (MachineState_T aMachineState)
2998{
2999 LogFlowThisFuncEnter();
3000 LogFlowThisFunc (("aMachineState=%d\n", aMachineState));
3001
3002 AutoCaller autoCaller (this);
3003 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
3004
3005 AutoLock alock (this);
3006
3007 if (mData->mMachineState != aMachineState)
3008 {
3009 mData->mMachineState = aMachineState;
3010
3011 RTTIMESPEC time;
3012 mData->mLastStateChange = RTTimeSpecGetMilli(RTTimeNow(&time));
3013
3014 mParent->onMachineStateChange (mData->mUuid, aMachineState);
3015 }
3016
3017 LogFlowThisFuncLeave();
3018 return S_OK;
3019}
3020
3021/**
3022 * Searches for a shared folder with the given logical name
3023 * in the collection of shared folders.
3024 *
3025 * @param aName logical name of the shared folder
3026 * @param aSharedFolder where to return the found object
3027 * @param aSetError whether to set the error info if the folder is
3028 * not found
3029 * @return
3030 * S_OK when found or E_INVALIDARG when not found
3031 *
3032 * @note
3033 * must be called from under the object's lock!
3034 */
3035HRESULT Machine::findSharedFolder (const BSTR aName,
3036 ComObjPtr <SharedFolder> &aSharedFolder,
3037 bool aSetError /* = false */)
3038{
3039 bool found = false;
3040 for (HWData::SharedFolderList::const_iterator it = mHWData->mSharedFolders.begin();
3041 !found && it != mHWData->mSharedFolders.end();
3042 ++ it)
3043 {
3044 AutoLock alock (*it);
3045 found = (*it)->name() == aName;
3046 if (found)
3047 aSharedFolder = *it;
3048 }
3049
3050 HRESULT rc = found ? S_OK : E_INVALIDARG;
3051
3052 if (aSetError && !found)
3053 setError (rc, tr ("Could not find a shared folder named '%ls'"), aName);
3054
3055 return rc;
3056}
3057
3058/**
3059 * Loads all the VM settings by walking down the <Machine> node.
3060 *
3061 * @param aRegistered true when the machine is being loaded on VirtualBox
3062 * startup
3063 *
3064 * @note This method is intended to be called only from init(), so it assumes
3065 * all machine data fields have appropriate default values when it is called.
3066 *
3067 * @note Doesn't lock any objects.
3068 */
3069HRESULT Machine::loadSettings (bool aRegistered)
3070{
3071 LogFlowThisFuncEnter();
3072 AssertReturn (mType == IsMachine, E_FAIL);
3073
3074 AutoCaller autoCaller (this);
3075 AssertReturn (autoCaller.state() == InInit, E_FAIL);
3076
3077 HRESULT rc = S_OK;
3078
3079 CFGHANDLE configLoader = NULL;
3080 char *loaderError = NULL;
3081 int vrc = CFGLDRLoad (&configLoader,
3082 Utf8Str (mData->mConfigFileFull), mData->mHandleCfgFile,
3083 XmlSchemaNS, true, cfgLdrEntityResolver,
3084 &loaderError);
3085 if (VBOX_FAILURE (vrc))
3086 {
3087 rc = setError (E_FAIL,
3088 tr ("Could not load the settings file '%ls' (%Vrc)%s%s"),
3089 mData->mConfigFileFull.raw(), vrc,
3090 loaderError ? ".\n" : "", loaderError ? loaderError : "");
3091
3092 if (loaderError)
3093 RTMemTmpFree (loaderError);
3094
3095 LogFlowThisFuncLeave();
3096 return rc;
3097 }
3098
3099 /*
3100 * When reading the XML, we assume it has been validated, so we don't
3101 * do any structural checks here, Just Assert() some things.
3102 */
3103
3104 CFGNODE machineNode = 0;
3105 CFGLDRGetNode (configLoader, "VirtualBox/Machine", 0, &machineNode);
3106
3107 do
3108 {
3109 ComAssertBreak (machineNode, rc = E_FAIL);
3110
3111 /* uuid (required) */
3112 Guid id;
3113 CFGLDRQueryUUID (machineNode, "uuid", id.ptr());
3114
3115 /* If the stored UUID is not empty, it means the registered machine
3116 * is being loaded. Compare the loaded UUID with the stored one taken
3117 * from the global registry. */
3118 if (!mData->mUuid.isEmpty())
3119 {
3120 if (mData->mUuid != id)
3121 {
3122 rc = setError (E_FAIL,
3123 tr ("Machine UUID {%Vuuid} in '%ls' doesn't match its "
3124 "UUID {%s} in the registry file '%ls'"),
3125 id.raw(), mData->mConfigFileFull.raw(),
3126 mData->mUuid.toString().raw(),
3127 mParent->settingsFileName().raw());
3128 break;
3129 }
3130 }
3131 else
3132 unconst (mData->mUuid) = id;
3133
3134 /* name (required) */
3135 CFGLDRQueryBSTR (machineNode, "name", mUserData->mName.asOutParam());
3136
3137 /* nameSync (optional, default is true) */
3138 {
3139 bool nameSync = true;
3140 CFGLDRQueryBool (machineNode, "nameSync", &nameSync);
3141 mUserData->mNameSync = nameSync;
3142 }
3143
3144 /* Description (optional, default is null) */
3145 {
3146 CFGNODE descNode = 0;
3147 CFGLDRGetChildNode (machineNode, "Description", 0, &descNode);
3148 if (descNode)
3149 {
3150 CFGLDRQueryBSTR (descNode, NULL,
3151 mUserData->mDescription.asOutParam());
3152 CFGLDRReleaseNode (descNode);
3153 }
3154 else
3155 mUserData->mDescription.setNull();
3156 }
3157
3158 /* OSType (required) */
3159 {
3160 Bstr osTypeId;
3161 CFGLDRQueryBSTR (machineNode, "OSType", osTypeId.asOutParam());
3162
3163 /* look up the object in our list */
3164 ComPtr <IGuestOSType> guestOSType;
3165 rc = mParent->FindGuestOSType (osTypeId, guestOSType.asOutParam());
3166 if (FAILED (rc))
3167 break;
3168
3169 mUserData->mOSType = guestOSType;
3170 }
3171
3172 /* stateFile (optional) */
3173 {
3174 Bstr stateFilePath;
3175 CFGLDRQueryBSTR (machineNode, "stateFile", stateFilePath.asOutParam());
3176 if (stateFilePath)
3177 {
3178 Utf8Str stateFilePathFull = stateFilePath;
3179 int vrc = calculateFullPath (stateFilePathFull, stateFilePathFull);
3180 if (VBOX_FAILURE (vrc))
3181 {
3182 rc = setError (E_FAIL,
3183 tr ("Invalid saved state file path: '%ls' (%Vrc)"),
3184 stateFilePath.raw(), vrc);
3185 break;
3186 }
3187 mSSData->mStateFilePath = stateFilePathFull;
3188 }
3189 else
3190 mSSData->mStateFilePath.setNull();
3191 }
3192
3193 /*
3194 * currentSnapshot ID (optional)
3195 * Note that due to XML Schema constaraints this attribute, when present,
3196 * will guaranteedly refer to an existing snapshot definition in XML
3197 */
3198 Guid currentSnapshotId;
3199 CFGLDRQueryUUID (machineNode, "currentSnapshot", currentSnapshotId.ptr());
3200
3201 /* snapshotFolder (optional) */
3202 {
3203 Bstr folder;
3204 CFGLDRQueryBSTR (machineNode, "snapshotFolder", folder.asOutParam());
3205 rc = COMSETTER(SnapshotFolder) (folder);
3206 if (FAILED (rc))
3207 break;
3208 }
3209
3210 /* lastStateChange (optional, for compatiblity) */
3211 {
3212 int64_t lastStateChange = 0;
3213 CFGLDRQueryDateTime (machineNode, "lastStateChange", &lastStateChange);
3214 if (lastStateChange == 0)
3215 {
3216 /// @todo (dmik) until lastStateChange is the required attribute,
3217 // we simply set it to the current time if missing in the config
3218 RTTIMESPEC time;
3219 lastStateChange = RTTimeSpecGetMilli (RTTimeNow (&time));
3220 }
3221 mData->mLastStateChange = lastStateChange;
3222 }
3223
3224 /* aborted (optional) */
3225 bool aborted = false;
3226 CFGLDRQueryBool (machineNode, "aborted", &aborted);
3227
3228 /* currentStateModified (optional, default is true) */
3229 mData->mCurrentStateModified = TRUE;
3230 {
3231 bool val = true;
3232 CFGLDRQueryBool (machineNode, "currentStateModified", &val);
3233 mData->mCurrentStateModified = val;
3234 }
3235
3236 /*
3237 * note: all mUserData members must be assigned prior this point because
3238 * we need to commit changes in order to let mUserData be shared by all
3239 * snapshot machine instances.
3240 */
3241 mUserData.commitCopy();
3242
3243 /* Snapshot node (optional) */
3244 {
3245 CFGNODE snapshotNode = 0;
3246 CFGLDRGetChildNode (machineNode, "Snapshot", 0, &snapshotNode);
3247 if (snapshotNode)
3248 {
3249 /* read all snapshots recursively */
3250 rc = loadSnapshot (snapshotNode, currentSnapshotId, NULL);
3251 CFGLDRReleaseNode (snapshotNode);
3252 if (FAILED (rc))
3253 break;
3254 }
3255 }
3256
3257 /* Hardware node (required) */
3258 {
3259 CFGNODE hardwareNode = 0;
3260 CFGLDRGetChildNode (machineNode, "Hardware", 0, &hardwareNode);
3261 ComAssertBreak (hardwareNode, rc = E_FAIL);
3262 rc = loadHardware (hardwareNode);
3263 CFGLDRReleaseNode (hardwareNode);
3264 if (FAILED (rc))
3265 break;
3266 }
3267
3268 /* HardDiskAttachments node (required) */
3269 {
3270 CFGNODE hdasNode = 0;
3271 CFGLDRGetChildNode (machineNode, "HardDiskAttachments", 0, &hdasNode);
3272 ComAssertBreak (hdasNode, rc = E_FAIL);
3273
3274 rc = loadHardDisks (hdasNode, aRegistered);
3275 CFGLDRReleaseNode (hdasNode);
3276 if (FAILED (rc))
3277 break;
3278 }
3279
3280 /*
3281 * NOTE: the assignment below must be the last thing to do,
3282 * otherwise it will be not possible to change the settings
3283 * somewehere in the code above because all setters will be
3284 * blocked by CHECK_SETTER()
3285 */
3286
3287 /* set the machine state to Aborted or Saved when appropriate */
3288 if (aborted)
3289 {
3290 Assert (!mSSData->mStateFilePath);
3291 mSSData->mStateFilePath.setNull();
3292
3293 mData->mMachineState = MachineState_Aborted;
3294 }
3295 else if (mSSData->mStateFilePath)
3296 {
3297 mData->mMachineState = MachineState_Saved;
3298 }
3299 }
3300 while (0);
3301
3302 if (machineNode)
3303 CFGLDRReleaseNode (machineNode);
3304
3305 CFGLDRFree (configLoader);
3306
3307 LogFlowThisFuncLeave();
3308 return rc;
3309}
3310
3311/**
3312 * Recursively loads all snapshots starting from the given.
3313 *
3314 * @param aNode <Snapshot> node
3315 * @param aCurSnapshotId current snapshot ID from the settings file
3316 * @param aParentSnapshot parent snapshot
3317 */
3318HRESULT Machine::loadSnapshot (CFGNODE aNode, const Guid &aCurSnapshotId,
3319 Snapshot *aParentSnapshot)
3320{
3321 AssertReturn (aNode, E_INVALIDARG);
3322 AssertReturn (mType == IsMachine, E_FAIL);
3323
3324 // create a snapshot machine object
3325 ComObjPtr <SnapshotMachine> snapshotMachine;
3326 snapshotMachine.createObject();
3327
3328 HRESULT rc = S_OK;
3329
3330 Guid uuid; // required
3331 CFGLDRQueryUUID (aNode, "uuid", uuid.ptr());
3332
3333 Bstr stateFilePath; // optional
3334 CFGLDRQueryBSTR (aNode, "stateFile", stateFilePath.asOutParam());
3335 if (stateFilePath)
3336 {
3337 Utf8Str stateFilePathFull = stateFilePath;
3338 int vrc = calculateFullPath (stateFilePathFull, stateFilePathFull);
3339 if (VBOX_FAILURE (vrc))
3340 return setError (E_FAIL,
3341 tr ("Invalid saved state file path: '%ls' (%Vrc)"),
3342 stateFilePath.raw(), vrc);
3343
3344 stateFilePath = stateFilePathFull;
3345 }
3346
3347 do
3348 {
3349 // Hardware node (required)
3350 CFGNODE hardwareNode = 0;
3351 CFGLDRGetChildNode (aNode, "Hardware", 0, &hardwareNode);
3352 ComAssertBreak (hardwareNode, rc = E_FAIL);
3353
3354 do
3355 {
3356 // HardDiskAttachments node (required)
3357 CFGNODE hdasNode = 0;
3358 CFGLDRGetChildNode (aNode, "HardDiskAttachments", 0, &hdasNode);
3359 ComAssertBreak (hdasNode, rc = E_FAIL);
3360
3361 // initialize the snapshot machine
3362 rc = snapshotMachine->init (this, hardwareNode, hdasNode,
3363 uuid, stateFilePath);
3364
3365 CFGLDRReleaseNode (hdasNode);
3366 }
3367 while (0);
3368
3369 CFGLDRReleaseNode (hardwareNode);
3370 }
3371 while (0);
3372
3373 if (FAILED (rc))
3374 return rc;
3375
3376 // create a snapshot object
3377 ComObjPtr <Snapshot> snapshot;
3378 snapshot.createObject();
3379
3380 {
3381 Bstr name; // required
3382 CFGLDRQueryBSTR (aNode, "name", name.asOutParam());
3383
3384 LONG64 timeStamp = 0; // required
3385 CFGLDRQueryDateTime (aNode, "timeStamp", &timeStamp);
3386
3387 Bstr description; // optional
3388 {
3389 CFGNODE descNode = 0;
3390 CFGLDRGetChildNode (aNode, "Description", 0, &descNode);
3391 if (descNode)
3392 {
3393 CFGLDRQueryBSTR (descNode, NULL, description.asOutParam());
3394 CFGLDRReleaseNode (descNode);
3395 }
3396 }
3397
3398 // initialize the snapshot
3399 rc = snapshot->init (uuid, name, description, timeStamp,
3400 snapshotMachine, aParentSnapshot);
3401 if (FAILED (rc))
3402 return rc;
3403 }
3404
3405 // memorize the first snapshot if necessary
3406 if (!mData->mFirstSnapshot)
3407 mData->mFirstSnapshot = snapshot;
3408
3409 // memorize the current snapshot when appropriate
3410 if (!mData->mCurrentSnapshot && snapshot->data().mId == aCurSnapshotId)
3411 mData->mCurrentSnapshot = snapshot;
3412
3413 // Snapshots node (optional)
3414 {
3415 CFGNODE snapshotsNode = 0;
3416 CFGLDRGetChildNode (aNode, "Snapshots", 0, &snapshotsNode);
3417 if (snapshotsNode)
3418 {
3419 unsigned cbDisks = 0;
3420 CFGLDRCountChildren (snapshotsNode, "Snapshot", &cbDisks);
3421 for (unsigned i = 0; i < cbDisks && SUCCEEDED (rc); i++)
3422 {
3423 CFGNODE snapshotNode;
3424 CFGLDRGetChildNode (snapshotsNode, "Snapshot", i, &snapshotNode);
3425 ComAssertBreak (snapshotNode, rc = E_FAIL);
3426
3427 rc = loadSnapshot (snapshotNode, aCurSnapshotId, snapshot);
3428
3429 CFGLDRReleaseNode (snapshotNode);
3430 }
3431
3432 CFGLDRReleaseNode (snapshotsNode);
3433 }
3434 }
3435
3436 return rc;
3437}
3438
3439/**
3440 * @param aNode <Hardware> node
3441 */
3442HRESULT Machine::loadHardware (CFGNODE aNode)
3443{
3444 AssertReturn (aNode, E_INVALIDARG);
3445 AssertReturn (mType == IsMachine || mType == IsSnapshotMachine, E_FAIL);
3446
3447 /* CPU node (currently not required) */
3448 {
3449 /* default value in case the node is not there */
3450 mHWData->mHWVirtExEnabled = TriStateBool_Default;
3451
3452 CFGNODE cpuNode = 0;
3453 CFGLDRGetChildNode (aNode, "CPU", 0, &cpuNode);
3454 if (cpuNode)
3455 {
3456 CFGNODE hwVirtExNode = 0;
3457 CFGLDRGetChildNode (cpuNode, "HardwareVirtEx", 0, &hwVirtExNode);
3458 if (hwVirtExNode)
3459 {
3460 Bstr hwVirtExEnabled;
3461 CFGLDRQueryBSTR (hwVirtExNode, "enabled", hwVirtExEnabled.asOutParam());
3462 if (hwVirtExEnabled == L"false")
3463 mHWData->mHWVirtExEnabled = TriStateBool_False;
3464 else if (hwVirtExEnabled == L"true")
3465 mHWData->mHWVirtExEnabled = TriStateBool_True;
3466 else
3467 mHWData->mHWVirtExEnabled = TriStateBool_Default;
3468 CFGLDRReleaseNode (hwVirtExNode);
3469 }
3470 CFGLDRReleaseNode (cpuNode);
3471 }
3472 }
3473
3474 /* Memory node (required) */
3475 {
3476 CFGNODE memoryNode = 0;
3477 CFGLDRGetChildNode (aNode, "Memory", 0, &memoryNode);
3478 ComAssertRet (memoryNode, E_FAIL);
3479
3480 uint32_t RAMSize;
3481 CFGLDRQueryUInt32 (memoryNode, "RAMSize", &RAMSize);
3482 mHWData->mMemorySize = RAMSize;
3483 CFGLDRReleaseNode (memoryNode);
3484 }
3485
3486 /* Boot node (required) */
3487 {
3488 /* reset all boot order positions to NoDevice */
3489 for (size_t i = 0; i < ELEMENTS (mHWData->mBootOrder); i++)
3490 mHWData->mBootOrder [i] = DeviceType_NoDevice;
3491
3492 CFGNODE bootNode = 0;
3493 CFGLDRGetChildNode (aNode, "Boot", 0, &bootNode);
3494 ComAssertRet (bootNode, E_FAIL);
3495
3496 HRESULT rc = S_OK;
3497
3498 unsigned cOrder;
3499 CFGLDRCountChildren (bootNode, "Order", &cOrder);
3500 for (unsigned i = 0; i < cOrder; i++)
3501 {
3502 CFGNODE orderNode = 0;
3503 CFGLDRGetChildNode (bootNode, "Order", i, &orderNode);
3504 ComAssertBreak (orderNode, rc = E_FAIL);
3505
3506 /* position (required) */
3507 /* position unicity is guaranteed by XML Schema */
3508 uint32_t position = 0;
3509 CFGLDRQueryUInt32 (orderNode, "position", &position);
3510 -- position;
3511 Assert (position < ELEMENTS (mHWData->mBootOrder));
3512
3513 /* device (required) */
3514 Bstr device;
3515 CFGLDRQueryBSTR (orderNode, "device", device.asOutParam());
3516 if (device == L"None")
3517 mHWData->mBootOrder [position] = DeviceType_NoDevice;
3518 else if (device == L"Floppy")
3519 mHWData->mBootOrder [position] = DeviceType_FloppyDevice;
3520 else if (device == L"DVD")
3521 mHWData->mBootOrder [position] = DeviceType_DVDDevice;
3522 else if (device == L"HardDisk")
3523 mHWData->mBootOrder [position] = DeviceType_HardDiskDevice;
3524 else if (device == L"Network")
3525 mHWData->mBootOrder [position] = DeviceType_NetworkDevice;
3526 else
3527 ComAssertMsgFailed (("Invalid device: %ls\n", device.raw()));
3528
3529 CFGLDRReleaseNode (orderNode);
3530 }
3531
3532 CFGLDRReleaseNode (bootNode);
3533 if (FAILED (rc))
3534 return rc;
3535 }
3536
3537 /* Display node (required) */
3538 {
3539 CFGNODE displayNode = 0;
3540 CFGLDRGetChildNode (aNode, "Display", 0, &displayNode);
3541 ComAssertRet (displayNode, E_FAIL);
3542
3543 uint32_t VRAMSize;
3544 CFGLDRQueryUInt32 (displayNode, "VRAMSize", &VRAMSize);
3545 mHWData->mVRAMSize = VRAMSize;
3546 CFGLDRReleaseNode (displayNode);
3547 }
3548
3549#ifdef VBOX_VRDP
3550 /* RemoteDisplay node (optional) */
3551 /// @todo (dmik) move the code to VRDPServer
3552 /// @todo r=sunlover: moved. dmik, please review.
3553 {
3554 CFGNODE remoteDisplayNode = 0;
3555 CFGLDRGetChildNode (aNode, "RemoteDisplay", 0, &remoteDisplayNode);
3556 if (remoteDisplayNode)
3557 {
3558 mVRDPServer->loadConfig (remoteDisplayNode);
3559 CFGLDRReleaseNode (remoteDisplayNode);
3560 }
3561 }
3562#endif
3563
3564 /* BIOS node (required) */
3565 {
3566 CFGNODE biosNode = 0;
3567 CFGLDRGetChildNode (aNode, "BIOS", 0, &biosNode);
3568 ComAssertRet (biosNode, E_FAIL);
3569
3570 HRESULT rc = S_OK;
3571
3572 do
3573 {
3574 /* ACPI */
3575 {
3576 CFGNODE acpiNode = 0;
3577 CFGLDRGetChildNode (biosNode, "ACPI", 0, &acpiNode);
3578 ComAssertBreak (acpiNode, rc = E_FAIL);
3579
3580 bool enabled;
3581 CFGLDRQueryBool (acpiNode, "enabled", &enabled);
3582 mBIOSSettings->COMSETTER(ACPIEnabled)(enabled);
3583 CFGLDRReleaseNode (acpiNode);
3584 }
3585
3586 /* IOAPIC */
3587 {
3588 CFGNODE ioapicNode = 0;
3589 CFGLDRGetChildNode (biosNode, "IOAPIC", 0, &ioapicNode);
3590 if (ioapicNode)
3591 {
3592 bool enabled;
3593 CFGLDRQueryBool (ioapicNode, "enabled", &enabled);
3594 mBIOSSettings->COMSETTER(IOAPICEnabled)(enabled);
3595 CFGLDRReleaseNode (ioapicNode);
3596 }
3597 }
3598
3599 /* Logo (optional) */
3600 {
3601 CFGNODE logoNode = 0;
3602 CFGLDRGetChildNode (biosNode, "Logo", 0, &logoNode);
3603 if (logoNode)
3604 {
3605 bool enabled = false;
3606 CFGLDRQueryBool (logoNode, "fadeIn", &enabled);
3607 mBIOSSettings->COMSETTER(LogoFadeIn)(enabled);
3608 CFGLDRQueryBool (logoNode, "fadeOut", &enabled);
3609 mBIOSSettings->COMSETTER(LogoFadeOut)(enabled);
3610
3611 uint32_t BIOSLogoDisplayTime;
3612 CFGLDRQueryUInt32 (logoNode, "displayTime", &BIOSLogoDisplayTime);
3613 mBIOSSettings->COMSETTER(LogoDisplayTime)(BIOSLogoDisplayTime);
3614
3615 Bstr logoPath;
3616 CFGLDRQueryBSTR (logoNode, "imagePath", logoPath.asOutParam());
3617 mBIOSSettings->COMSETTER(LogoImagePath)(logoPath);
3618
3619 CFGLDRReleaseNode (logoNode);
3620 }
3621 }
3622
3623 /* boot menu (optional) */
3624 {
3625 CFGNODE bootMenuNode = 0;
3626 CFGLDRGetChildNode (biosNode, "BootMenu", 0, &bootMenuNode);
3627 if (bootMenuNode)
3628 {
3629 Bstr modeStr;
3630 BIOSBootMenuMode_T mode;
3631 CFGLDRQueryBSTR (bootMenuNode, "mode", modeStr.asOutParam());
3632 if (modeStr == L"disabled")
3633 mode = BIOSBootMenuMode_Disabled;
3634 else if (modeStr == L"menuonly")
3635 mode = BIOSBootMenuMode_MenuOnly;
3636 else
3637 mode = BIOSBootMenuMode_MessageAndMenu;
3638 mBIOSSettings->COMSETTER(BootMenuMode)(mode);
3639
3640 CFGLDRReleaseNode (bootMenuNode);
3641 }
3642 }
3643 }
3644 while (0);
3645
3646 CFGLDRReleaseNode (biosNode);
3647 if (FAILED (rc))
3648 return rc;
3649 }
3650
3651 /* DVD drive (contains either Image or HostDrive or nothing) */
3652 /// @todo (dmik) move the code to DVDDrive
3653 {
3654 HRESULT rc = S_OK;
3655
3656 CFGNODE dvdDriveNode = 0;
3657 CFGLDRGetChildNode (aNode, "DVDDrive", 0, &dvdDriveNode);
3658 ComAssertRet (dvdDriveNode, E_FAIL);
3659
3660 bool fPassthrough;
3661 CFGLDRQueryBool(dvdDriveNode, "passthrough", &fPassthrough);
3662 mDVDDrive->COMSETTER(Passthrough)(fPassthrough);
3663
3664 CFGNODE typeNode = 0;
3665
3666 do
3667 {
3668 CFGLDRGetChildNode (dvdDriveNode, "Image", 0, &typeNode);
3669 if (typeNode)
3670 {
3671 Guid uuid;
3672 CFGLDRQueryUUID (typeNode, "uuid", uuid.ptr());
3673 rc = mDVDDrive->MountImage (uuid);
3674 }
3675 else
3676 {
3677 CFGLDRGetChildNode (dvdDriveNode, "HostDrive", 0, &typeNode);
3678 if (typeNode)
3679 {
3680 Bstr src;
3681 CFGLDRQueryBSTR (typeNode, "src", src.asOutParam());
3682
3683 /* find the correspoding object */
3684 ComPtr <IHost> host;
3685 rc = mParent->COMGETTER(Host) (host.asOutParam());
3686 ComAssertComRCBreak (rc, rc = rc);
3687
3688 ComPtr <IHostDVDDriveCollection> coll;
3689 rc = host->COMGETTER(DVDDrives) (coll.asOutParam());
3690 ComAssertComRCBreak (rc, rc = rc);
3691
3692 ComPtr <IHostDVDDrive> drive;
3693 rc = coll->FindByName (src, drive.asOutParam());
3694 if (SUCCEEDED (rc))
3695 rc = mDVDDrive->CaptureHostDrive (drive);
3696 else if (rc == E_INVALIDARG)
3697 {
3698 /* the host DVD drive is not currently available. we
3699 * assume it will be available later and create an
3700 * extra object now */
3701 ComObjPtr <HostDVDDrive> hostDrive;
3702 hostDrive.createObject();
3703 rc = hostDrive->init (src);
3704 ComAssertComRCBreak (rc, rc = rc);
3705 rc = mDVDDrive->CaptureHostDrive (hostDrive);
3706 }
3707 else
3708 ComAssertComRCBreak (rc, rc = rc);
3709 }
3710 }
3711 }
3712 while (0);
3713
3714 if (typeNode)
3715 CFGLDRReleaseNode (typeNode);
3716 CFGLDRReleaseNode (dvdDriveNode);
3717
3718 if (FAILED (rc))
3719 return rc;
3720 }
3721
3722 /* Floppy drive (contains either Image or HostDrive or nothing) */
3723 /// @todo (dmik) move the code to FloppyDrive
3724 {
3725 HRESULT rc = S_OK;
3726
3727 CFGNODE driveNode = 0;
3728 CFGLDRGetChildNode (aNode, "FloppyDrive", 0, &driveNode);
3729 ComAssertRet (driveNode, E_FAIL);
3730
3731 BOOL fFloppyEnabled = TRUE;
3732 CFGLDRQueryBool (driveNode, "enabled", (bool*)&fFloppyEnabled);
3733 rc = mFloppyDrive->COMSETTER(Enabled)(fFloppyEnabled);
3734
3735 CFGNODE typeNode = 0;
3736 do
3737 {
3738 CFGLDRGetChildNode (driveNode, "Image", 0, &typeNode);
3739 if (typeNode)
3740 {
3741 Guid uuid;
3742 CFGLDRQueryUUID (typeNode, "uuid", uuid.ptr());
3743 rc = mFloppyDrive->MountImage (uuid);
3744 }
3745 else
3746 {
3747 CFGLDRGetChildNode (driveNode, "HostDrive", 0, &typeNode);
3748 if (typeNode)
3749 {
3750 Bstr src;
3751 CFGLDRQueryBSTR (typeNode, "src", src.asOutParam());
3752
3753 /* find the correspoding object */
3754 ComPtr <IHost> host;
3755 rc = mParent->COMGETTER(Host) (host.asOutParam());
3756 ComAssertComRCBreak (rc, rc = rc);
3757
3758 ComPtr <IHostFloppyDriveCollection> coll;
3759 rc = host->COMGETTER(FloppyDrives) (coll.asOutParam());
3760 ComAssertComRCBreak (rc, rc = rc);
3761
3762 ComPtr <IHostFloppyDrive> drive;
3763 rc = coll->FindByName (src, drive.asOutParam());
3764 if (SUCCEEDED (rc))
3765 rc = mFloppyDrive->CaptureHostDrive (drive);
3766 else if (rc == E_INVALIDARG)
3767 {
3768 /* the host Floppy drive is not currently available. we
3769 * assume it will be available later and create an
3770 * extra object now */
3771 ComObjPtr <HostFloppyDrive> hostDrive;
3772 hostDrive.createObject();
3773 rc = hostDrive->init (src);
3774 ComAssertComRCBreak (rc, rc = rc);
3775 rc = mFloppyDrive->CaptureHostDrive (hostDrive);
3776 }
3777 else
3778 ComAssertComRCBreak (rc, rc = rc);
3779 }
3780 }
3781 }
3782 while (0);
3783
3784 if (typeNode)
3785 CFGLDRReleaseNode (typeNode);
3786 CFGLDRReleaseNode (driveNode);
3787
3788 if (FAILED (rc))
3789 return rc;
3790 }
3791
3792 /* USB Controller */
3793 {
3794 HRESULT rc = mUSBController->loadSettings (aNode);
3795 if (FAILED (rc))
3796 return rc;
3797 }
3798
3799 /* Network node (required) */
3800 /// @todo (dmik) move the code to NetworkAdapter
3801 {
3802 /* we assume that all network adapters are initially disabled
3803 * and detached */
3804
3805 CFGNODE networkNode = 0;
3806 CFGLDRGetChildNode (aNode, "Network", 0, &networkNode);
3807 ComAssertRet (networkNode, E_FAIL);
3808
3809 HRESULT rc = S_OK;
3810
3811 unsigned cAdapters = 0;
3812 CFGLDRCountChildren (networkNode, "Adapter", &cAdapters);
3813 for (unsigned i = 0; i < cAdapters; i++)
3814 {
3815 CFGNODE adapterNode = 0;
3816 CFGLDRGetChildNode (networkNode, "Adapter", i, &adapterNode);
3817 ComAssertBreak (adapterNode, rc = E_FAIL);
3818
3819 /* slot number (required) */
3820 /* slot unicity is guaranteed by XML Schema */
3821 uint32_t slot = 0;
3822 CFGLDRQueryUInt32 (adapterNode, "slot", &slot);
3823 Assert (slot < ELEMENTS (mNetworkAdapters));
3824
3825 /* type */
3826 Bstr adapterType;
3827 CFGLDRQueryBSTR (adapterNode, "type", adapterType.asOutParam());
3828 ComAssertBreak (adapterType, rc = E_FAIL);
3829
3830 /* enabled (required) */
3831 bool enabled = false;
3832 CFGLDRQueryBool (adapterNode, "enabled", &enabled);
3833 /* MAC address (can be null) */
3834 Bstr macAddr;
3835 CFGLDRQueryBSTR (adapterNode, "MACAddress", macAddr.asOutParam());
3836 /* cable (required) */
3837 bool cableConnected;
3838 CFGLDRQueryBool (adapterNode, "cable", &cableConnected);
3839 /* tracing (defaults to false) */
3840 bool traceEnabled;
3841 CFGLDRQueryBool (adapterNode, "trace", &traceEnabled);
3842 Bstr traceFile;
3843 CFGLDRQueryBSTR (adapterNode, "tracefile", traceFile.asOutParam());
3844
3845 mNetworkAdapters [slot]->COMSETTER(Enabled) (enabled);
3846 mNetworkAdapters [slot]->COMSETTER(MACAddress) (macAddr);
3847 mNetworkAdapters [slot]->COMSETTER(CableConnected) (cableConnected);
3848 mNetworkAdapters [slot]->COMSETTER(TraceEnabled) (traceEnabled);
3849 mNetworkAdapters [slot]->COMSETTER(TraceFile) (traceFile);
3850
3851 if (adapterType.compare(Bstr("Am79C970A")) == 0)
3852 mNetworkAdapters [slot]->COMSETTER(AdapterType)(NetworkAdapterType_NetworkAdapterAm79C970A);
3853 else if (adapterType.compare(Bstr("Am79C973")) == 0)
3854 mNetworkAdapters [slot]->COMSETTER(AdapterType)(NetworkAdapterType_NetworkAdapterAm79C973);
3855 else
3856 ComAssertBreak (0, rc = E_FAIL);
3857
3858 CFGNODE attachmentNode = 0;
3859 if (CFGLDRGetChildNode (adapterNode, "NAT", 0, &attachmentNode), attachmentNode)
3860 {
3861 mNetworkAdapters [slot]->AttachToNAT();
3862 }
3863 else
3864 if (CFGLDRGetChildNode (adapterNode, "HostInterface", 0, &attachmentNode), attachmentNode)
3865 {
3866 /* Host Interface Networking */
3867 Bstr name;
3868 CFGLDRQueryBSTR (attachmentNode, "name", name.asOutParam());
3869#ifdef __WIN__
3870 /* @name can be empty on Win32, but not null */
3871 ComAssertBreak (!name.isNull(), rc = E_FAIL);
3872#endif
3873 mNetworkAdapters [slot]->COMSETTER(HostInterface) (name);
3874#ifdef VBOX_WITH_UNIXY_TAP_NETWORKING
3875 Bstr tapSetupApp;
3876 CFGLDRQueryBSTR (attachmentNode, "TAPSetup", tapSetupApp.asOutParam());
3877 Bstr tapTerminateApp;
3878 CFGLDRQueryBSTR (attachmentNode, "TAPTerminate", tapTerminateApp.asOutParam());
3879
3880 mNetworkAdapters [slot]->COMSETTER(TAPSetupApplication) (tapSetupApp);
3881 mNetworkAdapters [slot]->COMSETTER(TAPTerminateApplication) (tapTerminateApp);
3882#endif // VBOX_WITH_UNIXY_TAP_NETWORKING
3883 mNetworkAdapters [slot]->AttachToHostInterface();
3884 }
3885 else
3886 if (CFGLDRGetChildNode(adapterNode, "InternalNetwork", 0, &attachmentNode), attachmentNode)
3887 {
3888 /* Internal Networking */
3889 Bstr name;
3890 CFGLDRQueryBSTR (attachmentNode, "name", name.asOutParam());
3891 ComAssertBreak (!name.isNull(), rc = E_FAIL);
3892 mNetworkAdapters[slot]->AttachToInternalNetwork();
3893 mNetworkAdapters[slot]->COMSETTER(InternalNetwork) (name);
3894 }
3895 else
3896 {
3897 /* Adapter has no children */
3898 mNetworkAdapters [slot]->Detach();
3899 }
3900 if (attachmentNode)
3901 CFGLDRReleaseNode (attachmentNode);
3902
3903 CFGLDRReleaseNode (adapterNode);
3904 }
3905
3906 CFGLDRReleaseNode (networkNode);
3907 if (FAILED (rc))
3908 return rc;
3909 }
3910
3911 /* AudioAdapter node (required) */
3912 /// @todo (dmik) move the code to AudioAdapter
3913 {
3914 CFGNODE audioAdapterNode = 0;
3915 CFGLDRGetChildNode (aNode, "AudioAdapter", 0, &audioAdapterNode);
3916 ComAssertRet (audioAdapterNode, E_FAIL);
3917
3918 // is the adapter enabled?
3919 bool enabled = false;
3920 CFGLDRQueryBool (audioAdapterNode, "enabled", &enabled);
3921 mAudioAdapter->COMSETTER(Enabled) (enabled);
3922 // now check the audio driver
3923 Bstr driver;
3924 CFGLDRQueryBSTR (audioAdapterNode, "driver", driver.asOutParam());
3925 AudioDriverType_T audioDriver;
3926 audioDriver = AudioDriverType_NullAudioDriver;
3927 if (driver == L"null")
3928 ; // Null has been set above
3929#ifdef __WIN__
3930 else if (driver == L"winmm")
3931#ifdef VBOX_WITH_WINMM
3932 audioDriver = AudioDriverType_WINMMAudioDriver;
3933#else
3934 // fall back to dsound
3935 audioDriver = AudioDriverType_DSOUNDAudioDriver;
3936#endif
3937 else if (driver == L"dsound")
3938 audioDriver = AudioDriverType_DSOUNDAudioDriver;
3939#endif // __WIN__
3940#ifdef __LINUX__
3941 else if (driver == L"oss")
3942 audioDriver = AudioDriverType_OSSAudioDriver;
3943 else if (driver == L"alsa")
3944#ifdef VBOX_WITH_ALSA
3945 audioDriver = AudioDriverType_ALSAAudioDriver;
3946#else
3947 // fall back to OSS
3948 audioDriver = AudioDriverType_OSSAudioDriver;
3949#endif
3950#endif // __LINUX__
3951 else
3952 AssertMsgFailed (("Invalid driver: %ls\n", driver.raw()));
3953 mAudioAdapter->COMSETTER(AudioDriver) (audioDriver);
3954
3955 CFGLDRReleaseNode (audioAdapterNode);
3956 }
3957
3958 /* Shared folders (optional) */
3959 /// @todo (dmik) make required on next format change!
3960 do
3961 {
3962 CFGNODE sharedFoldersNode = 0;
3963 CFGLDRGetChildNode (aNode, "SharedFolders", 0, &sharedFoldersNode);
3964
3965 if (!sharedFoldersNode)
3966 break;
3967
3968 HRESULT rc = S_OK;
3969
3970 unsigned cFolders = 0;
3971 CFGLDRCountChildren (sharedFoldersNode, "SharedFolder", &cFolders);
3972
3973 for (unsigned i = 0; i < cFolders; i++)
3974 {
3975 CFGNODE folderNode = 0;
3976 CFGLDRGetChildNode (sharedFoldersNode, "SharedFolder", i, &folderNode);
3977 ComAssertBreak (folderNode, rc = E_FAIL);
3978
3979 // folder logical name (required)
3980 Bstr name;
3981 CFGLDRQueryBSTR (folderNode, "name", name.asOutParam());
3982
3983 // folder host path (required)
3984 Bstr hostPath;
3985 CFGLDRQueryBSTR (folderNode, "hostPath", hostPath.asOutParam());
3986
3987 rc = CreateSharedFolder (name, hostPath);
3988 if (FAILED (rc))
3989 break;
3990
3991 CFGLDRReleaseNode (folderNode);
3992 }
3993
3994 CFGLDRReleaseNode (sharedFoldersNode);
3995 if (FAILED (rc))
3996 return rc;
3997 }
3998 while (0);
3999
4000 /* Clipboard node (currently not required) */
4001 /// @todo (dmik) make required on next format change!
4002 {
4003 /* default value in case the node is not there */
4004 mHWData->mClipboardMode = ClipboardMode_ClipDisabled;
4005
4006 CFGNODE clipNode = 0;
4007 CFGLDRGetChildNode (aNode, "Clipboard", 0, &clipNode);
4008 if (clipNode)
4009 {
4010 Bstr mode;
4011 CFGLDRQueryBSTR (clipNode, "mode", mode.asOutParam());
4012 if (mode == L"Disabled")
4013 mHWData->mClipboardMode = ClipboardMode_ClipDisabled;
4014 else if (mode == L"HostToGuest")
4015 mHWData->mClipboardMode = ClipboardMode_ClipHostToGuest;
4016 else if (mode == L"GuestToHost")
4017 mHWData->mClipboardMode = ClipboardMode_ClipGuestToHost;
4018 else if (mode == L"Bidirectional")
4019 mHWData->mClipboardMode = ClipboardMode_ClipBidirectional;
4020 else
4021 AssertMsgFailed (("%ls clipboard mode is invalid\n", mode.raw()));
4022 CFGLDRReleaseNode (clipNode);
4023 }
4024 }
4025
4026 return S_OK;
4027}
4028
4029/**
4030 * @param aNode <HardDiskAttachments> node
4031 * @param aRegistered true when the machine is being loaded on VirtualBox
4032 * startup, or when a snapshot is being loaded (wchich
4033 * currently can happen on startup only)
4034 * @param aSnapshotId pointer to the snapshot ID if this is a snapshot machine
4035 */
4036HRESULT Machine::loadHardDisks (CFGNODE aNode, bool aRegistered,
4037 const Guid *aSnapshotId /* = NULL */)
4038{
4039 AssertReturn (aNode, E_INVALIDARG);
4040 AssertReturn ((mType == IsMachine && aSnapshotId == NULL) ||
4041 (mType == IsSnapshotMachine && aSnapshotId != NULL), E_FAIL);
4042
4043 HRESULT rc = S_OK;
4044
4045 unsigned cbDisks = 0;
4046 CFGLDRCountChildren (aNode, "HardDiskAttachment", &cbDisks);
4047
4048 if (!aRegistered && cbDisks > 0)
4049 {
4050 /* when the machine is being loaded (opened) from a file, it cannot
4051 * have hard disks attached (this should not happen normally,
4052 * because we don't allow to attach hard disks to an unregistered
4053 * VM at all */
4054 return setError (E_FAIL,
4055 tr ("Unregistered machine '%ls' cannot have hard disks attached "
4056 "(found %d hard disk attachments)"),
4057 mUserData->mName.raw(), cbDisks);
4058 }
4059
4060 for (unsigned i = 0; i < cbDisks && SUCCEEDED (rc); ++ i)
4061 {
4062 CFGNODE hdNode;
4063 CFGLDRGetChildNode (aNode, "HardDiskAttachment", i, &hdNode);
4064 ComAssertRet (hdNode, E_FAIL);
4065
4066 do
4067 {
4068 /* hardDisk uuid (required) */
4069 Guid uuid;
4070 CFGLDRQueryUUID (hdNode, "hardDisk", uuid.ptr());
4071 /* bus (controller) type (required) */
4072 Bstr bus;
4073 CFGLDRQueryBSTR (hdNode, "bus", bus.asOutParam());
4074 /* device (required) */
4075 Bstr device;
4076 CFGLDRQueryBSTR (hdNode, "device", device.asOutParam());
4077
4078 /* find a hard disk by UUID */
4079 ComObjPtr <HardDisk> hd;
4080 rc = mParent->getHardDisk (uuid, hd);
4081 if (FAILED (rc))
4082 break;
4083
4084 AutoLock hdLock (hd);
4085
4086 if (!hd->machineId().isEmpty())
4087 {
4088 rc = setError (E_FAIL,
4089 tr ("Hard disk '%ls' with UUID {%s} is already "
4090 "attached to a machine with UUID {%s} (see '%ls')"),
4091 hd->toString().raw(), uuid.toString().raw(),
4092 hd->machineId().toString().raw(),
4093 mData->mConfigFileFull.raw());
4094 break;
4095 }
4096
4097 if (hd->type() == HardDiskType_ImmutableHardDisk)
4098 {
4099 rc = setError (E_FAIL,
4100 tr ("Immutable hard disk '%ls' with UUID {%s} cannot be "
4101 "directly attached to a machine (see '%ls')"),
4102 hd->toString().raw(), uuid.toString().raw(),
4103 mData->mConfigFileFull.raw());
4104 break;
4105 }
4106
4107 /* attach the device */
4108 DiskControllerType_T ctl = DiskControllerType_InvalidController;
4109 LONG dev = -1;
4110
4111 if (bus == L"ide0")
4112 {
4113 ctl = DiskControllerType_IDE0Controller;
4114 if (device == L"master")
4115 dev = 0;
4116 else if (device == L"slave")
4117 dev = 1;
4118 else
4119 ComAssertMsgFailedBreak (("Invalid device: %ls\n", device.raw()),
4120 rc = E_FAIL);
4121 }
4122 else if (bus == L"ide1")
4123 {
4124 ctl = DiskControllerType_IDE1Controller;
4125 if (device == L"master")
4126 rc = setError (E_FAIL, tr("Could not attach a disk as a master "
4127 "device on the secondary controller"));
4128 else if (device == L"slave")
4129 dev = 1;
4130 else
4131 ComAssertMsgFailedBreak (("Invalid device: %ls\n", device.raw()),
4132 rc = E_FAIL);
4133 }
4134 else
4135 ComAssertMsgFailedBreak (("Invalid bus: %ls\n", bus.raw()),
4136 rc = E_FAIL);
4137
4138 ComObjPtr <HardDiskAttachment> attachment;
4139 attachment.createObject();
4140 rc = attachment->init (hd, ctl, dev, false /* aDirty */);
4141 if (FAILED (rc))
4142 break;
4143
4144 /* associate the hard disk with this machine */
4145 hd->setMachineId (mData->mUuid);
4146
4147 /* associate the hard disk with the given snapshot ID */
4148 if (mType == IsSnapshotMachine)
4149 hd->setSnapshotId (*aSnapshotId);
4150
4151 mHDData->mHDAttachments.push_back (attachment);
4152 }
4153 while (0);
4154
4155 CFGLDRReleaseNode (hdNode);
4156 }
4157
4158 return rc;
4159}
4160
4161/**
4162 * Creates a config loader and loads the settings file.
4163 *
4164 * @param aIsNew |true| if a newly created settings file is to be opened
4165 * (must be the case only when called from #saveSettings())
4166 *
4167 * @note
4168 * XML Schema errors are not detected by this method because
4169 * it assumes that it will load settings from an exclusively locked
4170 * file (using a file handle) that was previously validated when opened
4171 * for the first time. Thus, this method should be used only when
4172 * it's necessary to modify (save) the settings file.
4173 *
4174 * @note The object must be locked at least for reading before calling
4175 * this method.
4176 */
4177HRESULT Machine::openConfigLoader (CFGHANDLE *aLoader, bool aIsNew /* = false */)
4178{
4179 AssertReturn (aLoader, E_FAIL);
4180
4181 /* The settings file must be created and locked at this point */
4182 ComAssertRet (isConfigLocked(), E_FAIL);
4183
4184 /* load the config file */
4185 int vrc = CFGLDRLoad (aLoader,
4186 Utf8Str (mData->mConfigFileFull), mData->mHandleCfgFile,
4187 aIsNew ? NULL : XmlSchemaNS, true, cfgLdrEntityResolver,
4188 NULL);
4189 ComAssertRCRet (vrc, E_FAIL);
4190
4191 return S_OK;
4192}
4193
4194/**
4195 * Closes the config loader previously created by #openConfigLoader().
4196 * If \a aSaveBeforeClose is true, then the config is saved to the settings file
4197 * before closing. If saving fails, a proper error message is set.
4198 *
4199 * @param aSaveBeforeClose whether to save the config before closing or not
4200 */
4201HRESULT Machine::closeConfigLoader (CFGHANDLE aLoader, bool aSaveBeforeClose)
4202{
4203 HRESULT rc = S_OK;
4204
4205 if (aSaveBeforeClose)
4206 {
4207 char *loaderError = NULL;
4208 int vrc = CFGLDRSave (aLoader, &loaderError);
4209 if (VBOX_FAILURE (vrc))
4210 {
4211 rc = setError (E_FAIL,
4212 tr ("Could not save the settings file '%ls' (%Vrc)%s%s"),
4213 mData->mConfigFileFull.raw(), vrc,
4214 loaderError ? ".\n" : "", loaderError ? loaderError : "");
4215 if (loaderError)
4216 RTMemTmpFree (loaderError);
4217 }
4218 }
4219
4220 CFGLDRFree (aLoader);
4221
4222 return rc;
4223}
4224
4225/**
4226 * Searches for a <Snapshot> node for the given snapshot.
4227 * If the search is successful, \a aSnapshotNode will contain the found node.
4228 * In this case, \a aSnapshotsNode can be NULL meaning the found node is a
4229 * direct child of \a aMachineNode.
4230 *
4231 * If the search fails, a failure is returned and both \a aSnapshotsNode and
4232 * \a aSnapshotNode are set to 0.
4233 *
4234 * @param aSnapshot snapshot to search for
4235 * @param aMachineNode <Machine> node to start from
4236 * @param aSnapshotsNode <Snapshots> node containing the found <Snapshot> node
4237 * (may be NULL if the caller is not interested)
4238 * @param aSnapshotNode found <Snapshot> node
4239 */
4240HRESULT Machine::findSnapshotNode (Snapshot *aSnapshot, CFGNODE aMachineNode,
4241 CFGNODE *aSnapshotsNode, CFGNODE *aSnapshotNode)
4242{
4243 AssertReturn (aSnapshot && aMachineNode && aSnapshotNode, E_FAIL);
4244
4245 if (aSnapshotsNode)
4246 *aSnapshotsNode = 0;
4247 *aSnapshotNode = 0;
4248
4249 // build the full uuid path (from the fist parent to the given snapshot)
4250 std::list <Guid> path;
4251 {
4252 ComObjPtr <Snapshot> parent = aSnapshot;
4253 while (parent)
4254 {
4255 path.push_front (parent->data().mId);
4256 parent = parent->parent();
4257 }
4258 }
4259
4260 CFGNODE snapshotsNode = aMachineNode;
4261 CFGNODE snapshotNode = 0;
4262
4263 for (std::list <Guid>::const_iterator it = path.begin();
4264 it != path.end();
4265 ++ it)
4266 {
4267 if (snapshotNode)
4268 {
4269 // proceed to the nested <Snapshots> node
4270 Assert (snapshotsNode);
4271 if (snapshotsNode != aMachineNode)
4272 {
4273 CFGLDRReleaseNode (snapshotsNode);
4274 snapshotsNode = 0;
4275 }
4276 CFGLDRGetChildNode (snapshotNode, "Snapshots", 0, &snapshotsNode);
4277 CFGLDRReleaseNode (snapshotNode);
4278 snapshotNode = 0;
4279 }
4280
4281 AssertReturn (snapshotsNode, E_FAIL);
4282
4283 unsigned count = 0, i = 0;
4284 CFGLDRCountChildren (snapshotsNode, "Snapshot", &count);
4285 for (; i < count; ++ i)
4286 {
4287 snapshotNode = 0;
4288 CFGLDRGetChildNode (snapshotsNode, "Snapshot", i, &snapshotNode);
4289 Guid id;
4290 CFGLDRQueryUUID (snapshotNode, "uuid", id.ptr());
4291 if (id == (*it))
4292 {
4293 // we keep (don't release) snapshotNode and snapshotsNode
4294 break;
4295 }
4296 CFGLDRReleaseNode (snapshotNode);
4297 snapshotNode = 0;
4298 }
4299
4300 if (i == count)
4301 {
4302 // the next uuid is not found, no need to continue...
4303 AssertFailed();
4304 if (snapshotsNode != aMachineNode)
4305 {
4306 CFGLDRReleaseNode (snapshotsNode);
4307 snapshotsNode = 0;
4308 }
4309 break;
4310 }
4311 }
4312
4313 // we must always succesfully find the node
4314 AssertReturn (snapshotNode, E_FAIL);
4315 AssertReturn (snapshotsNode, E_FAIL);
4316
4317 if (aSnapshotsNode)
4318 *aSnapshotsNode = snapshotsNode != aMachineNode ? snapshotsNode : 0;
4319 *aSnapshotNode = snapshotNode;
4320
4321 return S_OK;
4322}
4323
4324/**
4325 * Returns the snapshot with the given UUID or fails of no such snapshot.
4326 *
4327 * @param aId snapshot UUID to find (empty UUID refers the first snapshot)
4328 * @param aSnapshot where to return the found snapshot
4329 * @param aSetError true to set extended error info on failure
4330 */
4331HRESULT Machine::findSnapshot (const Guid &aId, ComObjPtr <Snapshot> &aSnapshot,
4332 bool aSetError /* = false */)
4333{
4334 if (!mData->mFirstSnapshot)
4335 {
4336 if (aSetError)
4337 return setError (E_FAIL,
4338 tr ("This machine does not have any snapshots"));
4339 return E_FAIL;
4340 }
4341
4342 if (aId.isEmpty())
4343 aSnapshot = mData->mFirstSnapshot;
4344 else
4345 aSnapshot = mData->mFirstSnapshot->findChildOrSelf (aId);
4346
4347 if (!aSnapshot)
4348 {
4349 if (aSetError)
4350 return setError (E_FAIL,
4351 tr ("Could not find a snapshot with UUID {%s}"),
4352 aId.toString().raw());
4353 return E_FAIL;
4354 }
4355
4356 return S_OK;
4357}
4358
4359/**
4360 * Returns the snapshot with the given name or fails of no such snapshot.
4361 *
4362 * @param aName snapshot name to find
4363 * @param aSnapshot where to return the found snapshot
4364 * @param aSetError true to set extended error info on failure
4365 */
4366HRESULT Machine::findSnapshot (const BSTR aName, ComObjPtr <Snapshot> &aSnapshot,
4367 bool aSetError /* = false */)
4368{
4369 AssertReturn (aName, E_INVALIDARG);
4370
4371 if (!mData->mFirstSnapshot)
4372 {
4373 if (aSetError)
4374 return setError (E_FAIL,
4375 tr ("This machine does not have any snapshots"));
4376 return E_FAIL;
4377 }
4378
4379 aSnapshot = mData->mFirstSnapshot->findChildOrSelf (aName);
4380
4381 if (!aSnapshot)
4382 {
4383 if (aSetError)
4384 return setError (E_FAIL,
4385 tr ("Could not find a snapshot named '%ls'"), aName);
4386 return E_FAIL;
4387 }
4388
4389 return S_OK;
4390}
4391
4392/**
4393 * Searches for an attachment that contains the given hard disk.
4394 * The hard disk must be associated with some VM and can be optionally
4395 * associated with some snapshot. If the attachment is stored in the snapshot
4396 * (i.e. the hard disk is associated with some snapshot), @a aSnapshot
4397 * will point to a non-null object on output.
4398 *
4399 * @param aHd hard disk to search an attachment for
4400 * @param aMachine where to store the hard disk's machine (can be NULL)
4401 * @param aSnapshot where to store the hard disk's snapshot (can be NULL)
4402 * @param aHda where to store the hard disk's attachment (can be NULL)
4403 *
4404 *
4405 * @note
4406 * It is assumed that the machine where the attachment is found,
4407 * is already placed to the Discarding state, when this method is called.
4408 * @note
4409 * The object returned in @a aHda is the attachment from the snapshot
4410 * machine if the hard disk is associated with the snapshot, not from the
4411 * primary machine object returned returned in @a aMachine.
4412 */
4413HRESULT Machine::findHardDiskAttachment (const ComObjPtr <HardDisk> &aHd,
4414 ComObjPtr <Machine> *aMachine,
4415 ComObjPtr <Snapshot> *aSnapshot,
4416 ComObjPtr <HardDiskAttachment> *aHda)
4417{
4418 AssertReturn (!aHd.isNull(), E_INVALIDARG);
4419
4420 Guid mid = aHd->machineId();
4421 Guid sid = aHd->snapshotId();
4422
4423 AssertReturn (!mid.isEmpty(), E_INVALIDARG);
4424
4425 ComObjPtr <Machine> m;
4426 mParent->getMachine (mid, m);
4427 ComAssertRet (!m.isNull(), E_FAIL);
4428
4429 HDData::HDAttachmentList *attachments = &m->mHDData->mHDAttachments;
4430
4431 ComObjPtr <Snapshot> s;
4432 if (!sid.isEmpty())
4433 {
4434 m->findSnapshot (sid, s);
4435 ComAssertRet (!s.isNull(), E_FAIL);
4436 attachments = &s->data().mMachine->mHDData->mHDAttachments;
4437 }
4438
4439 AssertReturn (attachments, E_FAIL);
4440
4441 for (HDData::HDAttachmentList::const_iterator it = attachments->begin();
4442 it != attachments->end();
4443 ++ it)
4444 {
4445 if ((*it)->hardDisk() == aHd)
4446 {
4447 if (aMachine) *aMachine = m;
4448 if (aSnapshot) *aSnapshot = s;
4449 if (aHda) *aHda = (*it);
4450 return S_OK;
4451 }
4452 }
4453
4454 ComAssertFailed();
4455 return E_FAIL;
4456}
4457
4458/**
4459 * Helper for #saveSettings. Cares about renaming the settings directory and
4460 * file if the machine name was changed and about creating a new settings file
4461 * if this is a new machine.
4462 *
4463 * @note Must be never called directly.
4464 *
4465 * @param aRenamed receives |true| if the name was changed and the settings
4466 * file was renamed as a result, or |false| otherwise. The
4467 * value makes sense only on success.
4468 * @param aNew receives |true| if a virgin settings file was created.
4469 */
4470HRESULT Machine::prepareSaveSettings (bool &aRenamed, bool &aNew)
4471{
4472 HRESULT rc = S_OK;
4473
4474 aRenamed = false;
4475
4476 /* if we're ready and isConfigLocked() is FALSE then it means
4477 * that no config file exists yet (we will create a virgin one) */
4478 aNew = !isConfigLocked();
4479
4480 /* attempt to rename the settings file if machine name is changed */
4481 if (mUserData->mNameSync &&
4482 mUserData.isBackedUp() &&
4483 mUserData.backedUpData()->mName != mUserData->mName)
4484 {
4485 aRenamed = true;
4486
4487 if (!aNew)
4488 {
4489 /* unlock the old config file */
4490 rc = unlockConfig();
4491 CheckComRCReturnRC (rc);
4492 }
4493
4494 bool dirRenamed = false;
4495 bool fileRenamed = false;
4496
4497 Utf8Str configFile, newConfigFile;
4498 Utf8Str configDir, newConfigDir;
4499
4500 do
4501 {
4502 int vrc = VINF_SUCCESS;
4503
4504 Utf8Str name = mUserData.backedUpData()->mName;
4505 Utf8Str newName = mUserData->mName;
4506
4507 configFile = mData->mConfigFileFull;
4508
4509 /* first, rename the directory if it matches the machine name */
4510 configDir = configFile;
4511 RTPathStripFilename (configDir.mutableRaw());
4512 newConfigDir = configDir;
4513 if (RTPathFilename (configDir) == name)
4514 {
4515 RTPathStripFilename (newConfigDir.mutableRaw());
4516 newConfigDir = Utf8StrFmt ("%s%c%s",
4517 newConfigDir.raw(), RTPATH_DELIMITER, newName.raw());
4518 /* new dir and old dir cannot be equal here because of 'if'
4519 * above and because name != newName */
4520 Assert (configDir != newConfigDir);
4521 if (!aNew)
4522 {
4523 /* perform real rename only if the machine is not new */
4524 vrc = RTPathRename (configDir.raw(), newConfigDir.raw(), 0);
4525 if (VBOX_FAILURE (vrc))
4526 {
4527 rc = setError (E_FAIL,
4528 tr ("Could not rename the directory '%s' to '%s' "
4529 "to save the settings file (%Vrc)"),
4530 configDir.raw(), newConfigDir.raw(), vrc);
4531 break;
4532 }
4533 dirRenamed = true;
4534 }
4535 }
4536
4537 newConfigFile = Utf8StrFmt ("%s%c%s.xml",
4538 newConfigDir.raw(), RTPATH_DELIMITER, newName.raw());
4539
4540 /* then try to rename the settings file itself */
4541 if (newConfigFile != configFile)
4542 {
4543 /* get the path to old settings file in renamed directory */
4544 configFile = Utf8StrFmt ("%s%c%s",
4545 newConfigDir.raw(), RTPATH_DELIMITER,
4546 RTPathFilename (configFile));
4547 if (!aNew)
4548 {
4549 /* perform real rename only if the machine is not new */
4550 vrc = RTFileRename (configFile.raw(), newConfigFile.raw(), 0);
4551 if (VBOX_FAILURE (vrc))
4552 {
4553 rc = setError (E_FAIL,
4554 tr ("Could not rename the settings file '%s' to '%s' "
4555 "(%Vrc)"),
4556 configFile.raw(), newConfigFile.raw(), vrc);
4557 break;
4558 }
4559 fileRenamed = true;
4560 }
4561 }
4562
4563 /* update mConfigFileFull amd mConfigFile */
4564 Bstr oldConfigFileFull = mData->mConfigFileFull;
4565 Bstr oldConfigFile = mData->mConfigFile;
4566 mData->mConfigFileFull = newConfigFile;
4567 /* try to get the relative path for mConfigFile */
4568 Utf8Str path = newConfigFile;
4569 mParent->calculateRelativePath (path, path);
4570 mData->mConfigFile = path;
4571
4572 /* last, try to update the global settings with the new path */
4573 if (mData->mRegistered)
4574 {
4575 rc = mParent->updateSettings (configDir, newConfigDir);
4576 if (FAILED (rc))
4577 {
4578 /* revert to old values */
4579 mData->mConfigFileFull = oldConfigFileFull;
4580 mData->mConfigFile = oldConfigFile;
4581 break;
4582 }
4583 }
4584
4585 /* update the snapshot folder */
4586 path = mUserData->mSnapshotFolderFull;
4587 if (RTPathStartsWith (path, configDir))
4588 {
4589 path = Utf8StrFmt ("%s%s", newConfigDir.raw(),
4590 path.raw() + configDir.length());
4591 mUserData->mSnapshotFolderFull = path;
4592 calculateRelativePath (path, path);
4593 mUserData->mSnapshotFolder = path;
4594 }
4595
4596 /* update the saved state file path */
4597 path = mSSData->mStateFilePath;
4598 if (RTPathStartsWith (path, configDir))
4599 {
4600 path = Utf8StrFmt ("%s%s", newConfigDir.raw(),
4601 path.raw() + configDir.length());
4602 mSSData->mStateFilePath = path;
4603 }
4604
4605 /* Update saved state file paths of all online snapshots.
4606 * Note that saveSettings() will recognize name change
4607 * and will save all snapshots in this case. */
4608 if (mData->mFirstSnapshot)
4609 mData->mFirstSnapshot->updateSavedStatePaths (configDir,
4610 newConfigDir);
4611 }
4612 while (0);
4613
4614 if (FAILED (rc))
4615 {
4616 /* silently try to rename everything back */
4617 if (fileRenamed)
4618 RTFileRename (newConfigFile.raw(), configFile.raw(), 0);
4619 if (dirRenamed)
4620 RTPathRename (newConfigDir.raw(), configDir.raw(), 0);
4621 }
4622
4623 if (!aNew)
4624 {
4625 /* lock the config again */
4626 HRESULT rc2 = lockConfig();
4627 if (SUCCEEDED (rc))
4628 rc = rc2;
4629 }
4630
4631 CheckComRCReturnRC (rc);
4632 }
4633
4634 if (aNew)
4635 {
4636 /* create a virgin config file */
4637 int vrc = VINF_SUCCESS;
4638
4639 /* ensure the settings directory exists */
4640 Utf8Str path = mData->mConfigFileFull;
4641 RTPathStripFilename (path.mutableRaw());
4642 if (!RTDirExists (path))
4643 {
4644 vrc = RTDirCreateFullPath (path, 0777);
4645 if (VBOX_FAILURE (vrc))
4646 {
4647 return setError (E_FAIL,
4648 tr ("Could not create a directory '%s' "
4649 "to save the settings file (%Vrc)"),
4650 path.raw(), vrc);
4651 }
4652 }
4653
4654 /* Note: open flags must correlated with RTFileOpen() in lockConfig() */
4655 path = Utf8Str (mData->mConfigFileFull);
4656 vrc = RTFileOpen (&mData->mHandleCfgFile, path,
4657 RTFILE_O_READWRITE | RTFILE_O_CREATE |
4658 RTFILE_O_DENY_WRITE);
4659 if (VBOX_SUCCESS (vrc))
4660 {
4661 vrc = RTFileWrite (mData->mHandleCfgFile,
4662 (void *) DefaultMachineConfig,
4663 sizeof (DefaultMachineConfig), NULL);
4664 }
4665 if (VBOX_FAILURE (vrc))
4666 {
4667 mData->mHandleCfgFile = NIL_RTFILE;
4668 return setError (E_FAIL,
4669 tr ("Could not create the settings file '%s' (%Vrc)"),
4670 path.raw(), vrc);
4671 }
4672 /* we do not close the file to simulate lockConfig() */
4673 }
4674
4675 return rc;
4676}
4677
4678/**
4679 * Saves machine data, user data and hardware data.
4680 *
4681 * @param aMarkCurStateAsModified
4682 * if true (default), mData->mCurrentStateModified will be set to
4683 * what #isReallyModified() returns prior to saving settings to a file,
4684 * otherwise the current value of mData->mCurrentStateModified will be
4685 * saved.
4686 * @param aInformCallbacksAnyway
4687 * if true, callbacks will be informed even if #isReallyModified()
4688 * returns false. This is necessary for cases when we change machine data
4689 * diectly, not through the backup()/commit() mechanism.
4690 *
4691 * @note Locks mParent (only in some cases, and only when #isConfigLocked() is
4692 * |TRUE|, see the #prepareSaveSettings() code for details) +
4693 * this object + children for writing.
4694 */
4695HRESULT Machine::saveSettings (bool aMarkCurStateAsModified /* = true */,
4696 bool aInformCallbacksAnyway /* = false */)
4697{
4698 LogFlowThisFuncEnter();
4699
4700 /// @todo (dmik) I guess we should lock all our child objects here
4701 // (such as mVRDPServer etc.) to ensure they are not changed
4702 // until completely saved to disk and committed
4703
4704 /// @todo (dmik) also, we need to delegate saving child objects' settings
4705 // to objects themselves to ensure operations 'commit + save changes'
4706 // are atomic (amd done from the object's lock so that nobody can change
4707 // settings again until completely saved).
4708
4709 AssertReturn (mType == IsMachine || mType == IsSessionMachine, E_FAIL);
4710
4711 bool wasModified;
4712
4713 if (aMarkCurStateAsModified)
4714 {
4715 /*
4716 * We ignore changes to user data when setting mCurrentStateModified
4717 * because the current state will not differ from the current snapshot
4718 * if only user data has been changed (user data is shared by all
4719 * snapshots).
4720 */
4721 mData->mCurrentStateModified = isReallyModified (true /* aIgnoreUserData */);
4722 wasModified = mUserData.hasActualChanges() || mData->mCurrentStateModified;
4723 }
4724 else
4725 {
4726 wasModified = isReallyModified();
4727 }
4728
4729 HRESULT rc = S_OK;
4730
4731 /* First, prepare to save settings. It will will care about renaming the
4732 * settings directory and file if the machine name was changed and about
4733 * creating a new settings file if this is a new machine. */
4734 bool isRenamed = false;
4735 bool isNew = false;
4736 rc = prepareSaveSettings (isRenamed, isNew);
4737 CheckComRCReturnRC (rc);
4738
4739 /* then, open the settings file */
4740 CFGHANDLE configLoader = 0;
4741 rc = openConfigLoader (&configLoader, isNew);
4742 CheckComRCReturnRC (rc);
4743
4744 /* save all snapshots when the machine name was changed since
4745 * it may affect saved state file paths for online snapshots (see
4746 * #openConfigLoader() for details) */
4747 bool updateAllSnapshots = isRenamed;
4748
4749 /* commit before saving, since it may change settings
4750 * (for example, perform fixup of lazy hard disk changes) */
4751 rc = commit();
4752 if (FAILED (rc))
4753 {
4754 closeConfigLoader (configLoader, false /* aSaveBeforeClose */);
4755 return rc;
4756 }
4757
4758 /* include hard disk changes to the modified flag */
4759 wasModified |= mHDData->mHDAttachmentsChanged;
4760 if (aMarkCurStateAsModified)
4761 mData->mCurrentStateModified |= BOOL (mHDData->mHDAttachmentsChanged);
4762
4763
4764 CFGNODE machineNode = 0;
4765 /* create if not exists */
4766 CFGLDRCreateNode (configLoader, "VirtualBox/Machine", &machineNode);
4767
4768 do
4769 {
4770 ComAssertBreak (machineNode, rc = E_FAIL);
4771
4772 /* uuid (required) */
4773 Assert (mData->mUuid);
4774 CFGLDRSetUUID (machineNode, "uuid", mData->mUuid.raw());
4775
4776 /* name (required) */
4777 Assert (!mUserData->mName.isEmpty());
4778 CFGLDRSetBSTR (machineNode, "name", mUserData->mName);
4779
4780 /* nameSync (optional, default is true) */
4781 if (!mUserData->mNameSync)
4782 CFGLDRSetBool (machineNode, "nameSync", false);
4783 else
4784 CFGLDRDeleteAttribute (machineNode, "nameSync");
4785
4786 /* Description node (optional) */
4787 if (!mUserData->mDescription.isNull())
4788 {
4789 CFGNODE descNode = 0;
4790 CFGLDRCreateChildNode (machineNode, "Description", &descNode);
4791 Assert (descNode);
4792 CFGLDRSetBSTR (descNode, NULL, mUserData->mDescription);
4793 CFGLDRReleaseNode (descNode);
4794 }
4795 else
4796 {
4797 CFGNODE descNode = 0;
4798 CFGLDRGetChildNode (machineNode, "Description", 0, &descNode);
4799 if (descNode)
4800 CFGLDRDeleteNode (descNode);
4801 }
4802
4803 /* OSType (required) */
4804 {
4805 Bstr osTypeID;
4806 rc = mUserData->mOSType->COMGETTER(Id) (osTypeID.asOutParam());
4807 ComAssertComRCBreak (rc, rc = rc);
4808 Assert (!osTypeID.isNull());
4809 CFGLDRSetBSTR (machineNode, "OSType", osTypeID);
4810 }
4811
4812 /* stateFile (optional) */
4813 if (mData->mMachineState == MachineState_Saved)
4814 {
4815 Assert (!mSSData->mStateFilePath.isEmpty());
4816 /* try to make the file name relative to the settings file dir */
4817 Utf8Str stateFilePath = mSSData->mStateFilePath;
4818 calculateRelativePath (stateFilePath, stateFilePath);
4819 CFGLDRSetString (machineNode, "stateFile", stateFilePath);
4820 }
4821 else
4822 {
4823 Assert (mSSData->mStateFilePath.isNull());
4824 CFGLDRDeleteAttribute (machineNode, "stateFile");
4825 }
4826
4827 /* currentSnapshot ID (optional) */
4828 if (!mData->mCurrentSnapshot.isNull())
4829 {
4830 Assert (!mData->mFirstSnapshot.isNull());
4831 CFGLDRSetUUID (machineNode, "currentSnapshot",
4832 mData->mCurrentSnapshot->data().mId);
4833 }
4834 else
4835 {
4836 Assert (mData->mFirstSnapshot.isNull());
4837 CFGLDRDeleteAttribute (machineNode, "currentSnapshot");
4838 }
4839
4840 /* snapshotFolder (optional) */
4841 if (mUserData->mSnapshotFolder)
4842 CFGLDRSetBSTR (machineNode, "snapshotFolder", mUserData->mSnapshotFolder);
4843 else
4844 CFGLDRDeleteAttribute (machineNode, "snapshotFolder");
4845
4846 /* currentStateModified (optional, default is yes) */
4847 if (!mData->mCurrentStateModified)
4848 CFGLDRSetBool (machineNode, "currentStateModified", false);
4849 else
4850 CFGLDRDeleteAttribute (machineNode, "currentStateModified");
4851
4852 /* lastStateChange */
4853 CFGLDRSetDateTime (machineNode, "lastStateChange",
4854 mData->mLastStateChange);
4855
4856 /* Hardware node (required) */
4857 {
4858 CFGNODE hwNode = 0;
4859 CFGLDRGetChildNode (machineNode, "Hardware", 0, &hwNode);
4860 /* first, delete the entire node if exists */
4861 if (hwNode)
4862 CFGLDRDeleteNode (hwNode);
4863 /* then recreate it */
4864 hwNode = 0;
4865 CFGLDRCreateChildNode (machineNode, "Hardware", &hwNode);
4866 ComAssertBreak (hwNode, rc = E_FAIL);
4867
4868 rc = saveHardware (hwNode);
4869
4870 CFGLDRReleaseNode (hwNode);
4871 if (FAILED (rc))
4872 break;
4873 }
4874
4875 /* HardDiskAttachments node (required) */
4876 {
4877 CFGNODE hdasNode = 0;
4878 CFGLDRGetChildNode (machineNode, "HardDiskAttachments", 0, &hdasNode);
4879 /* first, delete the entire node if exists */
4880 if (hdasNode)
4881 CFGLDRDeleteNode (hdasNode);
4882 /* then recreate it */
4883 hdasNode = 0;
4884 CFGLDRCreateChildNode (machineNode, "HardDiskAttachments", &hdasNode);
4885 ComAssertBreak (hdasNode, rc = E_FAIL);
4886
4887 rc = saveHardDisks (hdasNode);
4888
4889 CFGLDRReleaseNode (hdasNode);
4890 if (FAILED (rc))
4891 break;
4892 }
4893
4894 /* update all snapshots if requested */
4895 if (updateAllSnapshots)
4896 rc = saveSnapshotSettingsWorker (machineNode, NULL,
4897 SaveSS_UpdateAllOp);
4898 }
4899 while (0);
4900
4901 if (machineNode)
4902 CFGLDRReleaseNode (machineNode);
4903
4904 if (SUCCEEDED (rc))
4905 rc = closeConfigLoader (configLoader, true /* aSaveBeforeClose */);
4906 else
4907 closeConfigLoader (configLoader, false /* aSaveBeforeClose */);
4908
4909 if (FAILED (rc))
4910 {
4911 /*
4912 * backup arbitrary data item to cause #isModified() to still return
4913 * true in case of any error
4914 */
4915 mHWData.backup();
4916 }
4917
4918 if (wasModified || aInformCallbacksAnyway)
4919 {
4920 /*
4921 * Fire the data change event, even on failure (since we've already
4922 * committed all data). This is done only for SessionMachines because
4923 * mutable Machine instances are always not registered (i.e. private
4924 * to the client process that creates them) and thus don't need to
4925 * inform callbacks.
4926 */
4927 if (mType == IsSessionMachine)
4928 mParent->onMachineDataChange (mData->mUuid);
4929 }
4930
4931 LogFlowThisFunc (("rc=%08X\n", rc));
4932 LogFlowThisFuncLeave();
4933 return rc;
4934}
4935
4936/**
4937 * Wrapper for #saveSnapshotSettingsWorker() that opens the settings file
4938 * and locates the <Machine> node in there. See #saveSnapshotSettingsWorker()
4939 * for more details.
4940 *
4941 * @param aSnapshot Snapshot to operate on
4942 * @param aOpFlags Operation to perform, one of SaveSS_NoOp, SaveSS_AddOp
4943 * or SaveSS_UpdateAttrsOp possibly combined with
4944 * SaveSS_UpdateCurrentId.
4945 *
4946 * @note Locks this object for writing + other child objects.
4947 */
4948HRESULT Machine::saveSnapshotSettings (Snapshot *aSnapshot, int aOpFlags)
4949{
4950 AutoCaller autoCaller (this);
4951 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
4952
4953 AssertReturn (mType == IsMachine || mType == IsSessionMachine, E_FAIL);
4954
4955 AutoLock alock (this);
4956
4957 AssertReturn (isConfigLocked(), E_FAIL);
4958
4959 HRESULT rc = S_OK;
4960
4961 /* load the config file */
4962 CFGHANDLE configLoader = 0;
4963 rc = openConfigLoader (&configLoader);
4964 if (FAILED (rc))
4965 return rc;
4966
4967 CFGNODE machineNode = 0;
4968 CFGLDRGetNode (configLoader, "VirtualBox/Machine", 0, &machineNode);
4969
4970 do
4971 {
4972 ComAssertBreak (machineNode, rc = E_FAIL);
4973
4974 rc = saveSnapshotSettingsWorker (machineNode, aSnapshot, aOpFlags);
4975
4976 CFGLDRReleaseNode (machineNode);
4977 }
4978 while (0);
4979
4980 if (SUCCEEDED (rc))
4981 rc = closeConfigLoader (configLoader, true /* aSaveBeforeClose */);
4982 else
4983 closeConfigLoader (configLoader, false /* aSaveBeforeClose */);
4984
4985 return rc;
4986}
4987
4988/**
4989 * Performs the specified operation on the given snapshot
4990 * in the settings file represented by \a aMachineNode.
4991 *
4992 * If \a aOpFlags = SaveSS_UpdateAllOp, \a aSnapshot can be NULL to indicate
4993 * that the whole tree of the snapshots should be updated in <Machine>.
4994 * One particular case is when the last (and the only) snapshot should be
4995 * removed (it is so when both mCurrentSnapshot and mFirstSnapshot are NULL).
4996 *
4997 * \a aOp may be just SaveSS_UpdateCurrentId if only the currentSnapshot
4998 * attribute of <Machine> needs to be updated.
4999 *
5000 * @param aMachineNode <Machine> node in the opened settings file
5001 * @param aSnapshot Snapshot to operate on
5002 * @param aOpFlags Operation to perform, one of SaveSS_NoOp, SaveSS_AddOp
5003 * or SaveSS_UpdateAttrsOp possibly combined with
5004 * SaveSS_UpdateCurrentId.
5005 *
5006 * @note Must be called with this object locked for writing.
5007 * Locks child objects.
5008 */
5009HRESULT Machine::saveSnapshotSettingsWorker (CFGNODE aMachineNode,
5010 Snapshot *aSnapshot, int aOpFlags)
5011{
5012 AssertReturn (aMachineNode, E_FAIL);
5013
5014 AssertReturn (isLockedOnCurrentThread(), E_FAIL);
5015
5016 int op = aOpFlags & SaveSS_OpMask;
5017 AssertReturn (
5018 (aSnapshot && (op == SaveSS_AddOp || op == SaveSS_UpdateAttrsOp ||
5019 op == SaveSS_UpdateAllOp)) ||
5020 (!aSnapshot && ((op == SaveSS_NoOp && (aOpFlags & SaveSS_UpdateCurrentId)) ||
5021 op == SaveSS_UpdateAllOp)),
5022 E_FAIL);
5023
5024 HRESULT rc = S_OK;
5025
5026 bool recreateWholeTree = false;
5027
5028 do
5029 {
5030 if (op == SaveSS_NoOp)
5031 break;
5032
5033 /* quick path: recreate the whole tree of the snapshots */
5034 if (op == SaveSS_UpdateAllOp && !aSnapshot)
5035 {
5036 /* first, delete the entire root snapshot node if it exists */
5037 CFGNODE snapshotNode = 0;
5038 CFGLDRGetChildNode (aMachineNode, "Snapshot", 0, &snapshotNode);
5039 if (snapshotNode)
5040 CFGLDRDeleteNode (snapshotNode);
5041
5042 /*
5043 * second, if we have any snapshots left, substitute aSnapshot with
5044 * the first snapshot to recreate the whole tree, otherwise break
5045 */
5046 if (mData->mFirstSnapshot)
5047 {
5048 aSnapshot = mData->mFirstSnapshot;
5049 recreateWholeTree = true;
5050 }
5051 else
5052 break;
5053 }
5054
5055 Assert (!!aSnapshot);
5056 ComObjPtr <Snapshot> parent = aSnapshot->parent();
5057
5058 if (op == SaveSS_AddOp)
5059 {
5060 CFGNODE parentNode = 0;
5061
5062 if (parent)
5063 {
5064 rc = findSnapshotNode (parent, aMachineNode, NULL, &parentNode);
5065 if (FAILED (rc))
5066 break;
5067 ComAssertBreak (parentNode, rc = E_FAIL);
5068 }
5069
5070 do
5071 {
5072 CFGNODE snapshotsNode = 0;
5073
5074 if (parentNode)
5075 {
5076 CFGLDRCreateChildNode (parentNode, "Snapshots", &snapshotsNode);
5077 ComAssertBreak (snapshotsNode, rc = E_FAIL);
5078 }
5079 else
5080 snapshotsNode = aMachineNode;
5081 do
5082 {
5083 CFGNODE snapshotNode = 0;
5084 CFGLDRAppendChildNode (snapshotsNode, "Snapshot", &snapshotNode);
5085 ComAssertBreak (snapshotNode, rc = E_FAIL);
5086 rc = saveSnapshot (snapshotNode, aSnapshot, false /* aAttrsOnly */);
5087 CFGLDRReleaseNode (snapshotNode);
5088
5089 if (FAILED (rc))
5090 break;
5091
5092 /*
5093 * when a new snapshot is added, this means diffs were created
5094 * for every normal/immutable hard disk of the VM, so we need to
5095 * save the current hard disk attachments
5096 */
5097
5098 CFGNODE hdasNode = 0;
5099 CFGLDRGetChildNode (aMachineNode, "HardDiskAttachments", 0, &hdasNode);
5100 if (hdasNode)
5101 CFGLDRDeleteNode (hdasNode);
5102 CFGLDRCreateChildNode (aMachineNode, "HardDiskAttachments", &hdasNode);
5103 ComAssertBreak (hdasNode, rc = E_FAIL);
5104
5105 rc = saveHardDisks (hdasNode);
5106
5107 if (mHDData->mHDAttachments.size() != 0)
5108 {
5109 /*
5110 * If we have one or more attachments then we definitely
5111 * created diffs for them and associated new diffs with
5112 * current settngs. So, since we don't use saveSettings(),
5113 * we need to inform callbacks manually.
5114 */
5115 if (mType == IsSessionMachine)
5116 mParent->onMachineDataChange (mData->mUuid);
5117 }
5118
5119 CFGLDRReleaseNode (hdasNode);
5120 }
5121 while (0);
5122
5123 if (snapshotsNode != aMachineNode)
5124 CFGLDRReleaseNode (snapshotsNode);
5125 }
5126 while (0);
5127
5128 if (parentNode)
5129 CFGLDRReleaseNode (parentNode);
5130
5131 break;
5132 }
5133
5134 Assert (op == SaveSS_UpdateAttrsOp && !recreateWholeTree ||
5135 op == SaveSS_UpdateAllOp);
5136
5137 CFGNODE snapshotsNode = 0;
5138 CFGNODE snapshotNode = 0;
5139
5140 if (!recreateWholeTree)
5141 {
5142 rc = findSnapshotNode (aSnapshot, aMachineNode,
5143 &snapshotsNode, &snapshotNode);
5144 if (FAILED (rc))
5145 break;
5146 ComAssertBreak (snapshotNode, rc = E_FAIL);
5147 }
5148
5149 if (!snapshotsNode)
5150 snapshotsNode = aMachineNode;
5151
5152 if (op == SaveSS_UpdateAttrsOp)
5153 rc = saveSnapshot (snapshotNode, aSnapshot, true /* aAttrsOnly */);
5154 else do
5155 {
5156 if (snapshotNode)
5157 {
5158 CFGLDRDeleteNode (snapshotNode);
5159 snapshotNode = 0;
5160 }
5161 CFGLDRAppendChildNode (snapshotsNode, "Snapshot", &snapshotNode);
5162 ComAssertBreak (snapshotNode, rc = E_FAIL);
5163 rc = saveSnapshot (snapshotNode, aSnapshot, false /* aAttrsOnly */);
5164 }
5165 while (0);
5166
5167 CFGLDRReleaseNode (snapshotNode);
5168 if (snapshotsNode != aMachineNode)
5169 CFGLDRReleaseNode (snapshotsNode);
5170 }
5171 while (0);
5172
5173 if (SUCCEEDED (rc))
5174 {
5175 /* update currentSnapshot when appropriate */
5176 if (aOpFlags & SaveSS_UpdateCurrentId)
5177 {
5178 if (!mData->mCurrentSnapshot.isNull())
5179 CFGLDRSetUUID (aMachineNode, "currentSnapshot",
5180 mData->mCurrentSnapshot->data().mId);
5181 else
5182 CFGLDRDeleteAttribute (aMachineNode, "currentSnapshot");
5183 }
5184 if (aOpFlags & SaveSS_UpdateCurStateModified)
5185 {
5186 if (!mData->mCurrentStateModified)
5187 CFGLDRSetBool (aMachineNode, "currentStateModified", false);
5188 else
5189 CFGLDRDeleteAttribute (aMachineNode, "currentStateModified");
5190 }
5191 }
5192
5193 return rc;
5194}
5195
5196/**
5197 * Saves the given snapshot and all its children (unless \a aAttrsOnly is true).
5198 * It is assumed that the given node is empty (unless \a aAttrsOnly is true).
5199 *
5200 * @param aNode <Snapshot> node to save the snapshot to
5201 * @param aSnapshot snapshot to save
5202 * @param aAttrsOnly if true, only updatge user-changeable attrs
5203 */
5204HRESULT Machine::saveSnapshot (CFGNODE aNode, Snapshot *aSnapshot, bool aAttrsOnly)
5205{
5206 AssertReturn (aNode && aSnapshot, E_INVALIDARG);
5207 AssertReturn (mType == IsMachine || mType == IsSessionMachine, E_FAIL);
5208
5209 /* uuid (required) */
5210 if (!aAttrsOnly)
5211 CFGLDRSetUUID (aNode, "uuid", aSnapshot->data().mId);
5212
5213 /* name (required) */
5214 CFGLDRSetBSTR (aNode, "name", aSnapshot->data().mName);
5215
5216 /* timeStamp (required) */
5217 CFGLDRSetDateTime (aNode, "timeStamp", aSnapshot->data().mTimeStamp);
5218
5219 /* Description node (optional) */
5220 if (!aSnapshot->data().mDescription.isNull())
5221 {
5222 CFGNODE descNode = 0;
5223 CFGLDRCreateChildNode (aNode, "Description", &descNode);
5224 Assert (descNode);
5225 CFGLDRSetBSTR (descNode, NULL, aSnapshot->data().mDescription);
5226 CFGLDRReleaseNode (descNode);
5227 }
5228 else
5229 {
5230 CFGNODE descNode = 0;
5231 CFGLDRGetChildNode (aNode, "Description", 0, &descNode);
5232 if (descNode)
5233 CFGLDRDeleteNode (descNode);
5234 }
5235
5236 if (aAttrsOnly)
5237 return S_OK;
5238
5239 /* stateFile (optional) */
5240 if (aSnapshot->stateFilePath())
5241 {
5242 /* try to make the file name relative to the settings file dir */
5243 Utf8Str stateFilePath = aSnapshot->stateFilePath();
5244 calculateRelativePath (stateFilePath, stateFilePath);
5245 CFGLDRSetString (aNode, "stateFile", stateFilePath);
5246 }
5247
5248 {
5249 ComObjPtr <SnapshotMachine> snapshotMachine = aSnapshot->data().mMachine;
5250 ComAssertRet (!snapshotMachine.isNull(), E_FAIL);
5251
5252 /* save hardware */
5253 {
5254 CFGNODE hwNode = 0;
5255 CFGLDRCreateChildNode (aNode, "Hardware", &hwNode);
5256
5257 HRESULT rc = snapshotMachine->saveHardware (hwNode);
5258
5259 CFGLDRReleaseNode (hwNode);
5260 if (FAILED (rc))
5261 return rc;
5262 }
5263
5264 /* save hard disks */
5265 {
5266 CFGNODE hdasNode = 0;
5267 CFGLDRCreateChildNode (aNode, "HardDiskAttachments", &hdasNode);
5268
5269 HRESULT rc = snapshotMachine->saveHardDisks (hdasNode);
5270
5271 CFGLDRReleaseNode (hdasNode);
5272 if (FAILED (rc))
5273 return rc;
5274 }
5275 }
5276
5277 /* save children */
5278 {
5279 AutoLock listLock (aSnapshot->childrenLock());
5280
5281 if (aSnapshot->children().size())
5282 {
5283 CFGNODE snapshotsNode = 0;
5284 CFGLDRCreateChildNode (aNode, "Snapshots", &snapshotsNode);
5285
5286 HRESULT rc = S_OK;
5287
5288 for (Snapshot::SnapshotList::const_iterator it = aSnapshot->children().begin();
5289 it != aSnapshot->children().end() && SUCCEEDED (rc);
5290 ++ it)
5291 {
5292 CFGNODE snapshotNode = 0;
5293 CFGLDRCreateChildNode (snapshotsNode, "Snapshot", &snapshotNode);
5294
5295 rc = saveSnapshot (snapshotNode, (*it), aAttrsOnly);
5296
5297 CFGLDRReleaseNode (snapshotNode);
5298 }
5299
5300 CFGLDRReleaseNode (snapshotsNode);
5301 if (FAILED (rc))
5302 return rc;
5303 }
5304 }
5305
5306 return S_OK;
5307}
5308
5309/**
5310 * Creates Saves the VM hardware configuration.
5311 * It is assumed that the given node is empty.
5312 *
5313 * @param aNode <Hardware> node to save the VM hardware confguration to
5314 */
5315HRESULT Machine::saveHardware (CFGNODE aNode)
5316{
5317 AssertReturn (aNode, E_INVALIDARG);
5318
5319 HRESULT rc = S_OK;
5320
5321 /* CPU */
5322 {
5323 CFGNODE cpuNode = 0;
5324 CFGLDRCreateChildNode (aNode, "CPU", &cpuNode);
5325 CFGNODE hwVirtExNode = 0;
5326 CFGLDRCreateChildNode (cpuNode, "HardwareVirtEx", &hwVirtExNode);
5327 char *value = NULL;
5328 switch (mHWData->mHWVirtExEnabled)
5329 {
5330 case TriStateBool_False:
5331 value = "false";
5332 break;
5333 case TriStateBool_True:
5334 value = "true";
5335 break;
5336 default:
5337 value = "default";
5338 }
5339 CFGLDRSetString (hwVirtExNode, "enabled", value);
5340 CFGLDRReleaseNode (hwVirtExNode);
5341 CFGLDRReleaseNode (cpuNode);
5342 }
5343
5344 /* memory (required) */
5345 {
5346 CFGNODE memoryNode = 0;
5347 CFGLDRCreateChildNode (aNode, "Memory", &memoryNode);
5348 CFGLDRSetUInt32 (memoryNode, "RAMSize", mHWData->mMemorySize);
5349 CFGLDRReleaseNode (memoryNode);
5350 }
5351
5352 /* boot (required) */
5353 do
5354 {
5355 CFGNODE bootNode = 0;
5356 CFGLDRCreateChildNode (aNode, "Boot", &bootNode);
5357
5358 for (ULONG pos = 0; pos < ELEMENTS (mHWData->mBootOrder); pos ++)
5359 {
5360 const char *device = NULL;
5361 switch (mHWData->mBootOrder [pos])
5362 {
5363 case DeviceType_NoDevice:
5364 /* skip, this is allowed for <Order> nodes
5365 * when loading, the default value NoDevice will remain */
5366 continue;
5367 case DeviceType_FloppyDevice: device = "Floppy"; break;
5368 case DeviceType_DVDDevice: device = "DVD"; break;
5369 case DeviceType_HardDiskDevice: device = "HardDisk"; break;
5370 case DeviceType_NetworkDevice: device = "Network"; break;
5371 default:
5372 ComAssertMsgFailedBreak (("Invalid boot device: %d\n",
5373 mHWData->mBootOrder [pos]),
5374 rc = E_FAIL);
5375 }
5376 if (FAILED (rc))
5377 break;
5378
5379 CFGNODE orderNode = 0;
5380 CFGLDRAppendChildNode (bootNode, "Order", &orderNode);
5381
5382 CFGLDRSetUInt32 (orderNode, "position", pos + 1);
5383 CFGLDRSetString (orderNode, "device", device);
5384
5385 CFGLDRReleaseNode (orderNode);
5386 }
5387
5388 CFGLDRReleaseNode (bootNode);
5389 }
5390 while (0);
5391
5392 if (FAILED (rc))
5393 return rc;
5394
5395 /* display (required) */
5396 {
5397 CFGNODE displayNode = 0;
5398 CFGLDRCreateChildNode (aNode, "Display", &displayNode);
5399 CFGLDRSetUInt32 (displayNode, "VRAMSize", mHWData->mVRAMSize);
5400 CFGLDRReleaseNode (displayNode);
5401 }
5402
5403#ifdef VBOX_VRDP
5404 /* VRDP settings (optional) */
5405 /// @todo (dmik) move the code to VRDPServer
5406 /// @todo r=sunlover: moved. dmik, please review.
5407 {
5408 CFGNODE remoteDisplayNode = 0;
5409 CFGLDRCreateChildNode (aNode, "RemoteDisplay", &remoteDisplayNode);
5410
5411 if (remoteDisplayNode)
5412 {
5413 mVRDPServer->saveConfig (remoteDisplayNode);
5414 CFGLDRReleaseNode (remoteDisplayNode);
5415 }
5416 }
5417#endif
5418
5419 /* BIOS (required) */
5420 {
5421 CFGNODE biosNode = 0;
5422 CFGLDRCreateChildNode (aNode, "BIOS", &biosNode);
5423 {
5424 BOOL fSet;
5425 /* ACPI */
5426 CFGNODE acpiNode = 0;
5427 CFGLDRCreateChildNode (biosNode, "ACPI", &acpiNode);
5428 mBIOSSettings->COMGETTER(ACPIEnabled)(&fSet);
5429 CFGLDRSetBool (acpiNode, "enabled", !!fSet);
5430 CFGLDRReleaseNode (acpiNode);
5431
5432 /* IOAPIC */
5433 CFGNODE ioapicNode = 0;
5434 CFGLDRCreateChildNode (biosNode, "IOAPIC", &ioapicNode);
5435 mBIOSSettings->COMGETTER(IOAPICEnabled)(&fSet);
5436 CFGLDRSetBool (ioapicNode, "enabled", !!fSet);
5437 CFGLDRReleaseNode (ioapicNode);
5438
5439 /* BIOS logo (optional) **/
5440 CFGNODE logoNode = 0;
5441 CFGLDRCreateChildNode (biosNode, "Logo", &logoNode);
5442 mBIOSSettings->COMGETTER(LogoFadeIn)(&fSet);
5443 CFGLDRSetBool (logoNode, "fadeIn", !!fSet);
5444 mBIOSSettings->COMGETTER(LogoFadeOut)(&fSet);
5445 CFGLDRSetBool (logoNode, "fadeOut", !!fSet);
5446 ULONG ulDisplayTime;
5447 mBIOSSettings->COMGETTER(LogoDisplayTime)(&ulDisplayTime);
5448 CFGLDRSetUInt32 (logoNode, "displayTime", ulDisplayTime);
5449 Bstr logoPath;
5450 mBIOSSettings->COMGETTER(LogoImagePath)(logoPath.asOutParam());
5451 if (logoPath)
5452 CFGLDRSetBSTR (logoNode, "imagePath", logoPath);
5453 else
5454 CFGLDRDeleteAttribute (logoNode, "imagePath");
5455 CFGLDRReleaseNode (logoNode);
5456
5457 /* boot menu (optional) */
5458 CFGNODE bootMenuNode = 0;
5459 CFGLDRCreateChildNode (biosNode, "BootMenu", &bootMenuNode);
5460 BIOSBootMenuMode_T bootMenuMode;
5461 Bstr bootMenuModeStr;
5462 mBIOSSettings->COMGETTER(BootMenuMode)(&bootMenuMode);
5463 switch (bootMenuMode)
5464 {
5465 case BIOSBootMenuMode_Disabled:
5466 bootMenuModeStr = "disabled";
5467 break;
5468 case BIOSBootMenuMode_MenuOnly:
5469 bootMenuModeStr = "menuonly";
5470 break;
5471 default:
5472 bootMenuModeStr = "messageandmenu";
5473 }
5474 CFGLDRSetBSTR (bootMenuNode, "mode", bootMenuModeStr);
5475 CFGLDRReleaseNode (bootMenuNode);
5476 }
5477 CFGLDRReleaseNode(biosNode);
5478 }
5479
5480 /* DVD drive (required) */
5481 /// @todo (dmik) move the code to DVDDrive
5482 do
5483 {
5484 CFGNODE dvdNode = 0;
5485 CFGLDRCreateChildNode (aNode, "DVDDrive", &dvdNode);
5486
5487 BOOL fPassthrough;
5488 mDVDDrive->COMGETTER(Passthrough)(&fPassthrough);
5489 CFGLDRSetBool(dvdNode, "passthrough", !!fPassthrough);
5490
5491 switch (mDVDDrive->data()->mDriveState)
5492 {
5493 case DriveState_ImageMounted:
5494 {
5495 Assert (!mDVDDrive->data()->mDVDImage.isNull());
5496
5497 Guid id;
5498 rc = mDVDDrive->data()->mDVDImage->COMGETTER(Id) (id.asOutParam());
5499 Assert (!id.isEmpty());
5500
5501 CFGNODE imageNode = 0;
5502 CFGLDRCreateChildNode (dvdNode, "Image", &imageNode);
5503 CFGLDRSetUUID (imageNode, "uuid", id.ptr());
5504 CFGLDRReleaseNode (imageNode);
5505 break;
5506 }
5507 case DriveState_HostDriveCaptured:
5508 {
5509 Assert (!mDVDDrive->data()->mHostDrive.isNull());
5510
5511 Bstr name;
5512 rc = mDVDDrive->data()->mHostDrive->COMGETTER(Name) (name.asOutParam());
5513 Assert (!name.isEmpty());
5514
5515 CFGNODE hostDriveNode = 0;
5516 CFGLDRCreateChildNode (dvdNode, "HostDrive", &hostDriveNode);
5517 CFGLDRSetBSTR (hostDriveNode, "src", name);
5518 CFGLDRReleaseNode (hostDriveNode);
5519 break;
5520 }
5521 case DriveState_NotMounted:
5522 /* do nothing, i.e.leave the DVD drive node empty */
5523 break;
5524 default:
5525 ComAssertMsgFailedBreak (("Invalid DVD drive state: %d\n",
5526 mDVDDrive->data()->mDriveState),
5527 rc = E_FAIL);
5528 }
5529
5530 CFGLDRReleaseNode (dvdNode);
5531 }
5532 while (0);
5533
5534 if (FAILED (rc))
5535 return rc;
5536
5537 /* Flooppy drive (required) */
5538 /// @todo (dmik) move the code to DVDDrive
5539 do
5540 {
5541 CFGNODE floppyNode = 0;
5542 CFGLDRCreateChildNode (aNode, "FloppyDrive", &floppyNode);
5543
5544 BOOL fFloppyEnabled;
5545 rc = mFloppyDrive->COMGETTER(Enabled)(&fFloppyEnabled);
5546 CFGLDRSetBool (floppyNode, "enabled", !!fFloppyEnabled);
5547
5548 switch (mFloppyDrive->data()->mDriveState)
5549 {
5550 case DriveState_ImageMounted:
5551 {
5552 Assert (!mFloppyDrive->data()->mFloppyImage.isNull());
5553
5554 Guid id;
5555 rc = mFloppyDrive->data()->mFloppyImage->COMGETTER(Id) (id.asOutParam());
5556 Assert (!id.isEmpty());
5557
5558 CFGNODE imageNode = 0;
5559 CFGLDRCreateChildNode (floppyNode, "Image", &imageNode);
5560 CFGLDRSetUUID (imageNode, "uuid", id.ptr());
5561 CFGLDRReleaseNode (imageNode);
5562 break;
5563 }
5564 case DriveState_HostDriveCaptured:
5565 {
5566 Assert (!mFloppyDrive->data()->mHostDrive.isNull());
5567
5568 Bstr name;
5569 rc = mFloppyDrive->data()->mHostDrive->COMGETTER(Name) (name.asOutParam());
5570 Assert (!name.isEmpty());
5571
5572 CFGNODE hostDriveNode = 0;
5573 CFGLDRCreateChildNode (floppyNode, "HostDrive", &hostDriveNode);
5574 CFGLDRSetBSTR (hostDriveNode, "src", name);
5575 CFGLDRReleaseNode (hostDriveNode);
5576 break;
5577 }
5578 case DriveState_NotMounted:
5579 /* do nothing, i.e.leave the Floppy drive node empty */
5580 break;
5581 default:
5582 ComAssertMsgFailedBreak (("Invalid Floppy drive state: %d\n",
5583 mFloppyDrive->data()->mDriveState),
5584 rc = E_FAIL);
5585 }
5586
5587 CFGLDRReleaseNode (floppyNode);
5588 }
5589 while (0);
5590
5591 if (FAILED (rc))
5592 return rc;
5593
5594
5595 /* USB Controller (required) */
5596 rc = mUSBController->saveSettings (aNode);
5597 if (FAILED (rc))
5598 return rc;
5599
5600 /* Network adapters (required) */
5601 do
5602 {
5603 CFGNODE nwNode = 0;
5604 CFGLDRCreateChildNode (aNode, "Network", &nwNode);
5605
5606 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
5607 {
5608 CFGNODE networkAdapterNode = 0;
5609 CFGLDRAppendChildNode (nwNode, "Adapter", &networkAdapterNode);
5610
5611 CFGLDRSetUInt32 (networkAdapterNode, "slot", slot);
5612 CFGLDRSetBool (networkAdapterNode, "enabled",
5613 !!mNetworkAdapters [slot]->data()->mEnabled);
5614 CFGLDRSetBSTR (networkAdapterNode, "MACAddress",
5615 mNetworkAdapters [slot]->data()->mMACAddress);
5616 CFGLDRSetBool (networkAdapterNode, "cable",
5617 !!mNetworkAdapters [slot]->data()->mCableConnected);
5618
5619 if (mNetworkAdapters [slot]->data()->mTraceEnabled)
5620 CFGLDRSetBool (networkAdapterNode, "trace", true);
5621
5622 CFGLDRSetBSTR (networkAdapterNode, "tracefile",
5623 mNetworkAdapters [slot]->data()->mTraceFile);
5624
5625 switch (mNetworkAdapters [slot]->data()->mAdapterType)
5626 {
5627 case NetworkAdapterType_NetworkAdapterAm79C970A:
5628 CFGLDRSetString (networkAdapterNode, "type", "Am79C970A");
5629 break;
5630 case NetworkAdapterType_NetworkAdapterAm79C973:
5631 CFGLDRSetString (networkAdapterNode, "type", "Am79C973");
5632 break;
5633 default:
5634 ComAssertMsgFailedBreak (("Invalid network adapter type: %d\n",
5635 mNetworkAdapters [slot]->data()->mAdapterType),
5636 rc = E_FAIL);
5637 }
5638
5639 CFGNODE attachmentNode = 0;
5640 switch (mNetworkAdapters [slot]->data()->mAttachmentType)
5641 {
5642 case NetworkAttachmentType_NoNetworkAttachment:
5643 {
5644 /* do nothing -- empty content */
5645 break;
5646 }
5647 case NetworkAttachmentType_NATNetworkAttachment:
5648 {
5649 CFGLDRAppendChildNode (networkAdapterNode, "NAT", &attachmentNode);
5650 break;
5651 }
5652 case NetworkAttachmentType_HostInterfaceNetworkAttachment:
5653 {
5654 CFGLDRAppendChildNode (networkAdapterNode, "HostInterface", &attachmentNode);
5655 const Bstr &name = mNetworkAdapters [slot]->data()->mHostInterface;
5656#ifdef __WIN__
5657 Assert (!name.isNull());
5658#endif
5659#ifdef VBOX_WITH_UNIXY_TAP_NETWORKING
5660 if (!name.isEmpty())
5661#endif
5662 CFGLDRSetBSTR (attachmentNode, "name", name);
5663#ifdef VBOX_WITH_UNIXY_TAP_NETWORKING
5664 const Bstr &tapSetupApp =
5665 mNetworkAdapters [slot]->data()->mTAPSetupApplication;
5666 if (!tapSetupApp.isEmpty())
5667 CFGLDRSetBSTR (attachmentNode, "TAPSetup", tapSetupApp);
5668 const Bstr &tapTerminateApp =
5669 mNetworkAdapters [slot]->data()->mTAPTerminateApplication;
5670 if (!tapTerminateApp.isEmpty())
5671 CFGLDRSetBSTR (attachmentNode, "TAPTerminate", tapTerminateApp);
5672#endif /* VBOX_WITH_UNIXY_TAP_NETWORKING */
5673 break;
5674 }
5675 case NetworkAttachmentType_InternalNetworkAttachment:
5676 {
5677 CFGLDRAppendChildNode (networkAdapterNode, "InternalNetwork", &attachmentNode);
5678 const Bstr &name = mNetworkAdapters[slot]->data()->mInternalNetwork;
5679 Assert(!name.isNull());
5680 CFGLDRSetBSTR (attachmentNode, "name", name);
5681 break;
5682 }
5683 default:
5684 {
5685 ComAssertFailedBreak (rc = E_FAIL);
5686 break;
5687 }
5688 }
5689 if (attachmentNode)
5690 CFGLDRReleaseNode (attachmentNode);
5691
5692 CFGLDRReleaseNode (networkAdapterNode);
5693 }
5694
5695 CFGLDRReleaseNode (nwNode);
5696 }
5697 while (0);
5698
5699 if (FAILED (rc))
5700 return rc;
5701
5702 /* Audio adapter */
5703 do
5704 {
5705 CFGNODE adapterNode = 0;
5706 CFGLDRCreateChildNode (aNode, "AudioAdapter", &adapterNode);
5707
5708 switch (mAudioAdapter->data()->mAudioDriver)
5709 {
5710 case AudioDriverType_NullAudioDriver:
5711 {
5712 CFGLDRSetString (adapterNode, "driver", "null");
5713 break;
5714 }
5715#ifdef __WIN__
5716 case AudioDriverType_WINMMAudioDriver:
5717#ifdef VBOX_WITH_WINMM
5718 {
5719 CFGLDRSetString (adapterNode, "driver", "winmm");
5720 break;
5721 }
5722#endif
5723 case AudioDriverType_DSOUNDAudioDriver:
5724 {
5725 CFGLDRSetString (adapterNode, "driver", "dsound");
5726 break;
5727 }
5728#endif /* __WIN__ */
5729 case AudioDriverType_ALSAAudioDriver:
5730#ifdef VBOX_WITH_ALSA
5731 {
5732 CFGLDRSetString (adapterNode, "driver", "alsa");
5733 break;
5734 }
5735#endif
5736#ifdef __LINUX__
5737 case AudioDriverType_OSSAudioDriver:
5738 {
5739 CFGLDRSetString (adapterNode, "driver", "oss");
5740 break;
5741 }
5742#endif /* __LINUX__ */
5743 default:
5744 ComAssertMsgFailedBreak (("Wrong audio driver type! driver = %d\n",
5745 mAudioAdapter->data()->mAudioDriver),
5746 rc = E_FAIL);
5747 }
5748
5749 CFGLDRSetBool (adapterNode, "enabled", !!mAudioAdapter->data()->mEnabled);
5750
5751 CFGLDRReleaseNode (adapterNode);
5752 }
5753 while (0);
5754
5755 if (FAILED (rc))
5756 return rc;
5757
5758 /* Shared folders */
5759 do
5760 {
5761 CFGNODE sharedFoldersNode = 0;
5762 CFGLDRCreateChildNode (aNode, "SharedFolders", &sharedFoldersNode);
5763
5764 for (HWData::SharedFolderList::const_iterator it = mHWData->mSharedFolders.begin();
5765 it != mHWData->mSharedFolders.end();
5766 ++ it)
5767 {
5768 ComObjPtr <SharedFolder> folder = *it;
5769
5770 CFGNODE folderNode = 0;
5771 CFGLDRAppendChildNode (sharedFoldersNode, "SharedFolder", &folderNode);
5772
5773 /* all are mandatory */
5774 CFGLDRSetBSTR (folderNode, "name", folder->name());
5775 CFGLDRSetBSTR (folderNode, "hostPath", folder->hostPath());
5776
5777 CFGLDRReleaseNode (folderNode);
5778 }
5779
5780 CFGLDRReleaseNode (sharedFoldersNode);
5781 }
5782 while (0);
5783
5784 /* Clipboard */
5785 {
5786 CFGNODE clipNode = 0;
5787 CFGLDRCreateChildNode (aNode, "Clipboard", &clipNode);
5788
5789 char *mode = "Disabled";
5790 switch (mHWData->mClipboardMode)
5791 {
5792 case ClipboardMode_ClipDisabled:
5793 /* already assigned */
5794 break;
5795 case ClipboardMode_ClipHostToGuest:
5796 mode = "HostToGuest";
5797 break;
5798 case ClipboardMode_ClipGuestToHost:
5799 mode = "GuestToHost";
5800 break;
5801 case ClipboardMode_ClipBidirectional:
5802 mode = "Bidirectional";
5803 break;
5804 default:
5805 AssertMsgFailed (("Clipboard mode %d is invalid",
5806 mHWData->mClipboardMode));
5807 break;
5808 }
5809 CFGLDRSetString (clipNode, "mode", mode);
5810
5811 CFGLDRReleaseNode (clipNode);
5812 }
5813
5814 return rc;
5815}
5816
5817/**
5818 * Saves the hard disk confguration.
5819 * It is assumed that the given node is empty.
5820 *
5821 * @param aNode <HardDiskAttachments> node to save the hard disk confguration to
5822 */
5823HRESULT Machine::saveHardDisks (CFGNODE aNode)
5824{
5825 AssertReturn (aNode, E_INVALIDARG);
5826
5827 HRESULT rc = S_OK;
5828
5829 for (HDData::HDAttachmentList::const_iterator it = mHDData->mHDAttachments.begin();
5830 it != mHDData->mHDAttachments.end() && SUCCEEDED (rc);
5831 ++ it)
5832 {
5833 ComObjPtr <HardDiskAttachment> att = *it;
5834
5835 CFGNODE hdNode = 0;
5836 CFGLDRAppendChildNode (aNode, "HardDiskAttachment", &hdNode);
5837
5838 do
5839 {
5840 const char *bus = NULL;
5841 switch (att->controller())
5842 {
5843 case DiskControllerType_IDE0Controller: bus = "ide0"; break;
5844 case DiskControllerType_IDE1Controller: bus = "ide1"; break;
5845 default:
5846 ComAssertFailedBreak (rc = E_FAIL);
5847 }
5848 if (FAILED (rc))
5849 break;
5850
5851 const char *dev = NULL;
5852 switch (att->deviceNumber())
5853 {
5854 case 0: dev = "master"; break;
5855 case 1: dev = "slave"; break;
5856 default:
5857 ComAssertFailedBreak (rc = E_FAIL);
5858 }
5859 if (FAILED (rc))
5860 break;
5861
5862 CFGLDRSetUUID (hdNode, "hardDisk", att->hardDisk()->id());
5863 CFGLDRSetString (hdNode, "bus", bus);
5864 CFGLDRSetString (hdNode, "device", dev);
5865 }
5866 while (0);
5867
5868 CFGLDRReleaseNode (hdNode);
5869 }
5870
5871 return rc;
5872}
5873
5874/**
5875 * Saves machine state settings as defined by aFlags
5876 * (SaveSTS_* values).
5877 *
5878 * @param aFlags a combination of SaveSTS_* flags
5879 *
5880 * @note Locks objects!
5881 */
5882HRESULT Machine::saveStateSettings (int aFlags)
5883{
5884 if (aFlags == 0)
5885 return S_OK;
5886
5887 AutoCaller autoCaller (this);
5888 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
5889
5890 AutoLock alock (this);
5891
5892 /* load the config file */
5893 CFGHANDLE configLoader = 0;
5894 HRESULT rc = openConfigLoader (&configLoader);
5895 if (FAILED (rc))
5896 return rc;
5897
5898 CFGNODE machineNode = 0;
5899 CFGLDRGetNode (configLoader, "VirtualBox/Machine", 0, &machineNode);
5900
5901 do
5902 {
5903 ComAssertBreak (machineNode, rc = E_FAIL);
5904
5905 if (aFlags & SaveSTS_CurStateModified)
5906 {
5907 if (!mData->mCurrentStateModified)
5908 CFGLDRSetBool (machineNode, "currentStateModified", false);
5909 else
5910 CFGLDRDeleteAttribute (machineNode, "currentStateModified");
5911 }
5912
5913 if (aFlags & SaveSTS_StateFilePath)
5914 {
5915 if (mSSData->mStateFilePath)
5916 CFGLDRSetBSTR (machineNode, "stateFile", mSSData->mStateFilePath);
5917 else
5918 CFGLDRDeleteAttribute (machineNode, "stateFile");
5919 }
5920
5921 if (aFlags & SaveSTS_StateTimeStamp)
5922 {
5923 Assert (mData->mMachineState != MachineState_Aborted ||
5924 mSSData->mStateFilePath.isNull());
5925
5926 CFGLDRSetDateTime (machineNode, "lastStateChange",
5927 mData->mLastStateChange);
5928
5929 // set the aborted attribute when appropriate
5930 if (mData->mMachineState == MachineState_Aborted)
5931 CFGLDRSetBool (machineNode, "aborted", true);
5932 else
5933 CFGLDRDeleteAttribute (machineNode, "aborted");
5934 }
5935 }
5936 while (0);
5937
5938 if (machineNode)
5939 CFGLDRReleaseNode (machineNode);
5940
5941 if (SUCCEEDED (rc))
5942 rc = closeConfigLoader (configLoader, true /* aSaveBeforeClose */);
5943 else
5944 closeConfigLoader (configLoader, false /* aSaveBeforeClose */);
5945
5946 return rc;
5947}
5948
5949/**
5950 * Cleans up all differencing hard disks based on immutable hard disks.
5951 *
5952 * @note Locks objects!
5953 */
5954HRESULT Machine::wipeOutImmutableDiffs()
5955{
5956 AutoCaller autoCaller (this);
5957 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
5958
5959 AutoReaderLock alock (this);
5960
5961 AssertReturn (mData->mMachineState == MachineState_PoweredOff ||
5962 mData->mMachineState == MachineState_Aborted, E_FAIL);
5963
5964 for (HDData::HDAttachmentList::const_iterator it = mHDData->mHDAttachments.begin();
5965 it != mHDData->mHDAttachments.end();
5966 ++ it)
5967 {
5968 ComObjPtr <HardDisk> hd = (*it)->hardDisk();
5969 AutoLock hdLock (hd);
5970
5971 if(hd->isParentImmutable())
5972 {
5973 /// @todo (dmik) no error handling for now
5974 // (need async error reporting for this)
5975 hd->asVDI()->wipeOutImage();
5976 }
5977 }
5978
5979 return S_OK;
5980}
5981
5982/**
5983 * Fixes up lazy hard disk attachments by creating or deleting differencing
5984 * hard disks when machine settings are being committed.
5985 * Must be called only from #commit().
5986 *
5987 * @note Locks objects!
5988 */
5989HRESULT Machine::fixupHardDisks (bool aCommit)
5990{
5991 AutoCaller autoCaller (this);
5992 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
5993
5994 AutoLock alock (this);
5995
5996 /* no attac/detach operations -- nothing to do */
5997 if (!mHDData.isBackedUp())
5998 {
5999 mHDData->mHDAttachmentsChanged = false;
6000 return S_OK;
6001 }
6002
6003 AssertReturn (mData->mRegistered, E_FAIL);
6004
6005 if (aCommit)
6006 {
6007 /*
6008 * changes are being committed,
6009 * perform actual diff image creation, deletion etc.
6010 */
6011
6012 /* take a copy of backed up attachments (will modify it) */
6013 HDData::HDAttachmentList backedUp = mHDData.backedUpData()->mHDAttachments;
6014 /* list of new diffs created */
6015 std::list <ComObjPtr <HardDisk> > newDiffs;
6016
6017 HRESULT rc = S_OK;
6018
6019 /* go through current attachments */
6020 for (HDData::HDAttachmentList::const_iterator
6021 it = mHDData->mHDAttachments.begin();
6022 it != mHDData->mHDAttachments.end();
6023 ++ it)
6024 {
6025 ComObjPtr <HardDiskAttachment> hda = *it;
6026 ComObjPtr <HardDisk> hd = hda->hardDisk();
6027 AutoLock hdLock (hd);
6028
6029 if (!hda->isDirty())
6030 {
6031 /*
6032 * not dirty, therefore was either attached before backing up
6033 * or doesn't need any fixup (already fixed up); try to locate
6034 * this hard disk among backed up attachments and remove from
6035 * there to prevent it from being deassociated/deleted
6036 */
6037 HDData::HDAttachmentList::iterator oldIt;
6038 for (oldIt = backedUp.begin(); oldIt != backedUp.end(); ++ oldIt)
6039 if ((*oldIt)->hardDisk().equalsTo (hd))
6040 break;
6041 if (oldIt != backedUp.end())
6042 {
6043 /* remove from there */
6044 backedUp.erase (oldIt);
6045 Log3 (("FC: %ls found in old\n", hd->toString().raw()));
6046 }
6047 }
6048 else
6049 {
6050 /* dirty, determine what to do */
6051
6052 bool needDiff = false;
6053 bool searchAmongSnapshots = false;
6054
6055 switch (hd->type())
6056 {
6057 case HardDiskType_ImmutableHardDisk:
6058 {
6059 /* decrease readers increased in AttachHardDisk() */
6060 hd->releaseReader();
6061 Log3 (("FC: %ls released\n", hd->toString().raw()));
6062 /* indicate we need a diff (indirect attachment) */
6063 needDiff = true;
6064 break;
6065 }
6066 case HardDiskType_WritethroughHardDisk:
6067 {
6068 /* reset the dirty flag */
6069 hda->updateHardDisk (hd, false /* aDirty */);
6070 Log3 (("FC: %ls updated\n", hd->toString().raw()));
6071 break;
6072 }
6073 case HardDiskType_NormalHardDisk:
6074 {
6075 if (hd->snapshotId().isEmpty())
6076 {
6077 /* reset the dirty flag */
6078 hda->updateHardDisk (hd, false /* aDirty */);
6079 Log3 (("FC: %ls updated\n", hd->toString().raw()));
6080 }
6081 else
6082 {
6083 /* decrease readers increased in AttachHardDisk() */
6084 hd->releaseReader();
6085 Log3 (("FC: %ls released\n", hd->toString().raw()));
6086 /* indicate we need a diff (indirect attachment) */
6087 needDiff = true;
6088 /* search for the most recent base among snapshots */
6089 searchAmongSnapshots = true;
6090 }
6091 break;
6092 }
6093 }
6094
6095 if (!needDiff)
6096 continue;
6097
6098 bool createDiff = false;
6099
6100 /*
6101 * see whether any previously attached hard disk has the
6102 * the currently attached one (Normal or Independent) as
6103 * the root
6104 */
6105
6106 HDData::HDAttachmentList::iterator foundIt = backedUp.end();
6107
6108 for (HDData::HDAttachmentList::iterator it = backedUp.begin();
6109 it != backedUp.end();
6110 ++ it)
6111 {
6112 if ((*it)->hardDisk()->root().equalsTo (hd))
6113 {
6114 /*
6115 * matched dev and ctl (i.e. attached to the same place)
6116 * will win and immediately stop the search; otherwise
6117 * the first attachment that matched the hd only will
6118 * be used
6119 */
6120 if ((*it)->deviceNumber() == hda->deviceNumber() &&
6121 (*it)->controller() == hda->controller())
6122 {
6123 foundIt = it;
6124 break;
6125 }
6126 else
6127 if (foundIt == backedUp.end())
6128 {
6129 /*
6130 * not an exact match; ensure there is no exact match
6131 * among other current attachments referring the same
6132 * root (to prevent this attachmend from reusing the
6133 * hard disk of the other attachment that will later
6134 * give the exact match or already gave it before)
6135 */
6136 bool canReuse = true;
6137 for (HDData::HDAttachmentList::const_iterator
6138 it2 = mHDData->mHDAttachments.begin();
6139 it2 != mHDData->mHDAttachments.end();
6140 ++ it2)
6141 {
6142 if ((*it2)->deviceNumber() == (*it)->deviceNumber() &&
6143 (*it2)->controller() == (*it)->controller() &&
6144 (*it2)->hardDisk()->root().equalsTo (hd))
6145 {
6146 /*
6147 * the exact match, either non-dirty or dirty
6148 * one refers the same root: in both cases
6149 * we cannot reuse the hard disk, so break
6150 */
6151 canReuse = false;
6152 break;
6153 }
6154 }
6155
6156 if (canReuse)
6157 foundIt = it;
6158 }
6159 }
6160 }
6161
6162 if (foundIt != backedUp.end())
6163 {
6164 /* found either one or another, reuse the diff */
6165 hda->updateHardDisk ((*foundIt)->hardDisk(),
6166 false /* aDirty */);
6167 Log3 (("FC: %ls reused as %ls\n", hd->toString().raw(),
6168 (*foundIt)->hardDisk()->toString().raw()));
6169 /* remove from there */
6170 backedUp.erase (foundIt);
6171 }
6172 else
6173 {
6174 /* was not attached, need a diff */
6175 createDiff = true;
6176 }
6177
6178 if (!createDiff)
6179 continue;
6180
6181 ComObjPtr <HardDisk> baseHd = hd;
6182
6183 if (searchAmongSnapshots)
6184 {
6185 /*
6186 * find the most recent diff based on the currently
6187 * attached root (Normal hard disk) among snapshots
6188 */
6189
6190 ComObjPtr <Snapshot> snap = mData->mCurrentSnapshot;
6191
6192 while (snap)
6193 {
6194 AutoLock snapLock (snap);
6195
6196 const HDData::HDAttachmentList &snapAtts =
6197 snap->data().mMachine->hdData()->mHDAttachments;
6198
6199 HDData::HDAttachmentList::const_iterator foundIt = snapAtts.end();
6200
6201 for (HDData::HDAttachmentList::const_iterator
6202 it = snapAtts.begin(); it != snapAtts.end(); ++ it)
6203 {
6204 if ((*it)->hardDisk()->root().equalsTo (hd))
6205 {
6206 /*
6207 * matched dev and ctl (i.e. attached to the same place)
6208 * will win and immediately stop the search; otherwise
6209 * the first attachment that matched the hd only will
6210 * be used
6211 */
6212 if ((*it)->deviceNumber() == hda->deviceNumber() &&
6213 (*it)->controller() == hda->controller())
6214 {
6215 foundIt = it;
6216 break;
6217 }
6218 else
6219 if (foundIt == snapAtts.end())
6220 foundIt = it;
6221 }
6222 }
6223
6224 if (foundIt != snapAtts.end())
6225 {
6226 /* the most recent diff has been found, use as a base */
6227 baseHd = (*foundIt)->hardDisk();
6228 Log3 (("FC: %ls: recent found %ls\n",
6229 hd->toString().raw(), baseHd->toString().raw()));
6230 break;
6231 }
6232
6233 snap = snap->parent();
6234 }
6235 }
6236
6237 /* create a new diff for the hard disk being indirectly attached */
6238
6239 AutoLock baseHdLock (baseHd);
6240 baseHd->addReader();
6241
6242 ComObjPtr <HVirtualDiskImage> vdi;
6243 rc = baseHd->createDiffHardDisk (mUserData->mSnapshotFolderFull,
6244 mData->mUuid, vdi, NULL);
6245 baseHd->releaseReader();
6246 CheckComRCBreakRC (rc);
6247
6248 newDiffs.push_back (ComObjPtr <HardDisk> (vdi));
6249
6250 /* update the attachment and reset the dirty flag */
6251 hda->updateHardDisk (ComObjPtr <HardDisk> (vdi),
6252 false /* aDirty */);
6253 Log3 (("FC: %ls: diff created %ls\n",
6254 baseHd->toString().raw(), vdi->toString().raw()));
6255 }
6256 }
6257
6258 if (FAILED (rc))
6259 {
6260 /* delete diffs we created */
6261 for (std::list <ComObjPtr <HardDisk> >::const_iterator
6262 it = newDiffs.begin(); it != newDiffs.end(); ++ it)
6263 {
6264 /*
6265 * unregisterDiffHardDisk() is supposed to delete and uninit
6266 * the differencing hard disk
6267 */
6268 mParent->unregisterDiffHardDisk (*it);
6269 /* too bad if we fail here, but nothing to do, just continue */
6270 }
6271
6272 /* the best is to rollback the changes... */
6273 mHDData.rollback();
6274 mHDData->mHDAttachmentsChanged = false;
6275 Log3 (("FC: ROLLED BACK\n"));
6276 return rc;
6277 }
6278
6279 /*
6280 * go through the rest of old attachments and delete diffs
6281 * or deassociate hard disks from machines (they will become detached)
6282 */
6283 for (HDData::HDAttachmentList::iterator
6284 it = backedUp.begin(); it != backedUp.end(); ++ it)
6285 {
6286 ComObjPtr <HardDiskAttachment> hda = *it;
6287 ComObjPtr <HardDisk> hd = hda->hardDisk();
6288 AutoLock hdLock (hd);
6289
6290 if (hd->isDifferencing())
6291 {
6292 /*
6293 * unregisterDiffHardDisk() is supposed to delete and uninit
6294 * the differencing hard disk
6295 */
6296 Log3 (("FC: %ls diff deleted\n", hd->toString().raw()));
6297 rc = mParent->unregisterDiffHardDisk (hd);
6298 /*
6299 * too bad if we fail here, but nothing to do, just continue
6300 * (the last rc will be returned to the caller though)
6301 */
6302 }
6303 else
6304 {
6305 /* deassociate from this machine */
6306 Log3 (("FC: %ls deassociated\n", hd->toString().raw()));
6307 hd->setMachineId (Guid());
6308 }
6309 }
6310
6311 /* commit all the changes */
6312 mHDData->mHDAttachmentsChanged = mHDData.hasActualChanges();
6313 mHDData.commit();
6314 Log3 (("FC: COMMITTED\n"));
6315
6316 return rc;
6317 }
6318
6319 /*
6320 * changes are being rolled back,
6321 * go trhough all current attachments and fix up dirty ones
6322 * the way it is done in DetachHardDisk()
6323 */
6324
6325 for (HDData::HDAttachmentList::iterator it = mHDData->mHDAttachments.begin();
6326 it != mHDData->mHDAttachments.end();
6327 ++ it)
6328 {
6329 ComObjPtr <HardDiskAttachment> hda = *it;
6330 ComObjPtr <HardDisk> hd = hda->hardDisk();
6331 AutoLock hdLock (hd);
6332
6333 if (hda->isDirty())
6334 {
6335 switch (hd->type())
6336 {
6337 case HardDiskType_ImmutableHardDisk:
6338 {
6339 /* decrease readers increased in AttachHardDisk() */
6340 hd->releaseReader();
6341 Log3 (("FR: %ls released\n", hd->toString().raw()));
6342 break;
6343 }
6344 case HardDiskType_WritethroughHardDisk:
6345 {
6346 /* deassociate from this machine */
6347 hd->setMachineId (Guid());
6348 Log3 (("FR: %ls deassociated\n", hd->toString().raw()));
6349 break;
6350 }
6351 case HardDiskType_NormalHardDisk:
6352 {
6353 if (hd->snapshotId().isEmpty())
6354 {
6355 /* deassociate from this machine */
6356 hd->setMachineId (Guid());
6357 Log3 (("FR: %ls deassociated\n", hd->toString().raw()));
6358 }
6359 else
6360 {
6361 /* decrease readers increased in AttachHardDisk() */
6362 hd->releaseReader();
6363 Log3 (("FR: %ls released\n", hd->toString().raw()));
6364 }
6365
6366 break;
6367 }
6368 }
6369 }
6370 }
6371
6372 /* rollback all the changes */
6373 mHDData.rollback();
6374 Log3 (("FR: ROLLED BACK\n"));
6375
6376 return S_OK;
6377}
6378
6379/**
6380 * Creates differencing hard disks for all normal hard disks
6381 * and replaces attachments to refer to created disks.
6382 * Used when taking a snapshot or when discarding the current state.
6383 *
6384 * @param aSnapshotId ID of the snapshot being taken
6385 * or NULL if the current state is being discarded
6386 * @param aFolder folder where to create diff. hard disks
6387 * @param aProgress progress object to run (must contain at least as
6388 * many operations left as the number of VDIs attached)
6389 * @param aOnline whether the machine is online (i.e., when the EMT
6390 * thread is paused, OR when current hard disks are
6391 * marked as busy for some other reason)
6392 *
6393 * @note
6394 * The progress object is not marked as completed, neither on success
6395 * nor on failure. This is a responsibility of the caller.
6396 *
6397 * @note Locks mParent + this object for writing
6398 */
6399HRESULT Machine::createSnapshotDiffs (const Guid *aSnapshotId,
6400 const Bstr &aFolder,
6401 const ComObjPtr <Progress> &aProgress,
6402 bool aOnline)
6403{
6404 AssertReturn (!aFolder.isEmpty(), E_FAIL);
6405
6406 AutoCaller autoCaller (this);
6407 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
6408
6409 /* accessing mParent methods below needs mParent lock */
6410 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
6411
6412 HRESULT rc = S_OK;
6413
6414 // first pass: check accessibility before performing changes
6415 if (!aOnline)
6416 {
6417 for (HDData::HDAttachmentList::const_iterator it = mHDData->mHDAttachments.begin();
6418 it != mHDData->mHDAttachments.end();
6419 ++ it)
6420 {
6421 ComObjPtr <HardDiskAttachment> hda = *it;
6422 ComObjPtr <HardDisk> hd = hda->hardDisk();
6423 AutoLock hdLock (hd);
6424
6425 ComAssertMsgBreak (hd->type() == HardDiskType_NormalHardDisk,
6426 ("Invalid hard disk type %d\n", hd->type()),
6427 rc = E_FAIL);
6428
6429 ComAssertMsgBreak (!hd->isParentImmutable() ||
6430 hd->storageType() == HardDiskStorageType_VirtualDiskImage,
6431 ("Invalid hard disk storage type %d\n", hd->storageType()),
6432 rc = E_FAIL);
6433
6434 Bstr accessError;
6435 rc = hd->getAccessible (accessError);
6436 CheckComRCBreakRC (rc);
6437
6438 if (!accessError.isNull())
6439 {
6440 rc = setError (E_FAIL,
6441 tr ("Hard disk '%ls' is not accessible (%ls)"),
6442 hd->toString().raw(), accessError.raw());
6443 break;
6444 }
6445 }
6446 CheckComRCReturnRC (rc);
6447 }
6448
6449 HDData::HDAttachmentList attachments;
6450
6451 // second pass: perform changes
6452 for (HDData::HDAttachmentList::const_iterator it = mHDData->mHDAttachments.begin();
6453 it != mHDData->mHDAttachments.end();
6454 ++ it)
6455 {
6456 ComObjPtr <HardDiskAttachment> hda = *it;
6457 ComObjPtr <HardDisk> hd = hda->hardDisk();
6458 AutoLock hdLock (hd);
6459
6460 ComObjPtr <HardDisk> parent = hd->parent();
6461 AutoLock parentHdLock (parent);
6462
6463 ComObjPtr <HardDisk> newHd;
6464
6465 // clear busy flag if the VM is online
6466 if (aOnline)
6467 hd->clearBusy();
6468 // increase readers
6469 hd->addReader();
6470
6471 if (hd->isParentImmutable())
6472 {
6473 aProgress->advanceOperation (Bstr (Utf8StrFmt (
6474 tr ("Preserving immutable hard disk '%ls'"),
6475 parent->toString (true /* aShort */).raw())));
6476
6477 parentHdLock.unlock();
6478 alock.leave();
6479
6480 // create a copy of the independent diff
6481 ComObjPtr <HVirtualDiskImage> vdi;
6482 rc = hd->asVDI()->cloneDiffImage (aFolder, mData->mUuid, vdi,
6483 aProgress);
6484 newHd = vdi;
6485
6486 alock.enter();
6487 parentHdLock.lock();
6488
6489 // decrease readers (hd is no more used for reading in any case)
6490 hd->releaseReader();
6491 }
6492 else
6493 {
6494 // checked in the first pass
6495 Assert (hd->type() == HardDiskType_NormalHardDisk);
6496
6497 aProgress->advanceOperation (Bstr (Utf8StrFmt (
6498 tr ("Creating a differencing hard disk for '%ls'"),
6499 hd->root()->toString (true /* aShort */).raw())));
6500
6501 parentHdLock.unlock();
6502 alock.leave();
6503
6504 // create a new diff for the image being attached
6505 ComObjPtr <HVirtualDiskImage> vdi;
6506 rc = hd->createDiffHardDisk (aFolder, mData->mUuid, vdi, aProgress);
6507 newHd = vdi;
6508
6509 alock.enter();
6510 parentHdLock.lock();
6511
6512 if (SUCCEEDED (rc))
6513 {
6514 // if online, hd must keep a reader referece
6515 if (!aOnline)
6516 hd->releaseReader();
6517 }
6518 else
6519 {
6520 // decrease readers
6521 hd->releaseReader();
6522 }
6523 }
6524
6525 if (SUCCEEDED (rc))
6526 {
6527 ComObjPtr <HardDiskAttachment> newHda;
6528 newHda.createObject();
6529 rc = newHda->init (newHd, hda->controller(), hda->deviceNumber(),
6530 false /* aDirty */);
6531
6532 if (SUCCEEDED (rc))
6533 {
6534 // associate the snapshot id with the old hard disk
6535 if (hd->type() != HardDiskType_WritethroughHardDisk && aSnapshotId)
6536 hd->setSnapshotId (*aSnapshotId);
6537
6538 // add the new attachment
6539 attachments.push_back (newHda);
6540
6541 // if online, newHd must be marked as busy
6542 if (aOnline)
6543 newHd->setBusy();
6544 }
6545 }
6546
6547 if (FAILED (rc))
6548 {
6549 // set busy flag back if the VM is online
6550 if (aOnline)
6551 hd->setBusy();
6552 break;
6553 }
6554 }
6555
6556 if (SUCCEEDED (rc))
6557 {
6558 // replace the whole list of attachments with the new one
6559 mHDData->mHDAttachments = attachments;
6560 }
6561 else
6562 {
6563 // delete those diffs we've just created
6564 for (HDData::HDAttachmentList::const_iterator it = attachments.begin();
6565 it != attachments.end();
6566 ++ it)
6567 {
6568 ComObjPtr <HardDisk> hd = (*it)->hardDisk();
6569 AutoLock hdLock (hd);
6570 Assert (hd->children().size() == 0);
6571 Assert (hd->isDifferencing());
6572 // unregisterDiffHardDisk() is supposed to delete and uninit
6573 // the differencing hard disk
6574 mParent->unregisterDiffHardDisk (hd);
6575 }
6576 }
6577
6578 return rc;
6579}
6580
6581/**
6582 * Deletes differencing hard disks created by createSnapshotDiffs() in case
6583 * if snapshot creation was failed.
6584 *
6585 * @param aSnapshot failed snapshot
6586 *
6587 * @note Locks mParent + this object for writing.
6588 */
6589HRESULT Machine::deleteSnapshotDiffs (const ComObjPtr <Snapshot> &aSnapshot)
6590{
6591 AssertReturn (!aSnapshot.isNull(), E_FAIL);
6592
6593 AutoCaller autoCaller (this);
6594 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
6595
6596 /* accessing mParent methods below needs mParent lock */
6597 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
6598
6599 /* short cut: check whether attachments are all the same */
6600 if (mHDData->mHDAttachments == aSnapshot->data().mMachine->mHDData->mHDAttachments)
6601 return S_OK;
6602
6603 HRESULT rc = S_OK;
6604
6605 for (HDData::HDAttachmentList::const_iterator it = mHDData->mHDAttachments.begin();
6606 it != mHDData->mHDAttachments.end();
6607 ++ it)
6608 {
6609 ComObjPtr <HardDiskAttachment> hda = *it;
6610 ComObjPtr <HardDisk> hd = hda->hardDisk();
6611 AutoLock hdLock (hd);
6612
6613 ComObjPtr <HardDisk> parent = hd->parent();
6614 AutoLock parentHdLock (parent);
6615
6616 if (!parent || parent->snapshotId() != aSnapshot->data().mId)
6617 continue;
6618
6619 /* must not have children */
6620 ComAssertRet (hd->children().size() == 0, E_FAIL);
6621
6622 /* deassociate the old hard disk from the given snapshot's ID */
6623 parent->setSnapshotId (Guid());
6624
6625 /* unregisterDiffHardDisk() is supposed to delete and uninit
6626 * the differencing hard disk */
6627 rc = mParent->unregisterDiffHardDisk (hd);
6628 /* continue on error */
6629 }
6630
6631 /* restore the whole list of attachments from the failed snapshot */
6632 mHDData->mHDAttachments = aSnapshot->data().mMachine->mHDData->mHDAttachments;
6633
6634 return rc;
6635}
6636
6637/**
6638 * Helper to lock the machine configuration for write access.
6639 *
6640 * @return S_OK or E_FAIL and sets error info on failure
6641 *
6642 * @note Doesn't lock anything (must be called from this object's lock)
6643 */
6644HRESULT Machine::lockConfig()
6645{
6646 HRESULT rc = S_OK;
6647
6648 if (!isConfigLocked())
6649 {
6650 /* open the associated config file */
6651 int vrc = RTFileOpen (&mData->mHandleCfgFile,
6652 Utf8Str (mData->mConfigFileFull),
6653 RTFILE_O_READWRITE | RTFILE_O_OPEN |
6654 RTFILE_O_DENY_WRITE);
6655 if (VBOX_FAILURE (vrc))
6656 mData->mHandleCfgFile = NIL_RTFILE;
6657 }
6658
6659 LogFlowThisFunc (("mConfigFile={%ls}, mHandleCfgFile=%d, rc=%08X\n",
6660 mData->mConfigFileFull.raw(), mData->mHandleCfgFile, rc));
6661 return rc;
6662}
6663
6664/**
6665 * Helper to unlock the machine configuration from write access
6666 *
6667 * @return S_OK
6668 *
6669 * @note Doesn't lock anything.
6670 * @note Not thread safe (must be called from this object's lock).
6671 */
6672HRESULT Machine::unlockConfig()
6673{
6674 HRESULT rc = S_OK;
6675
6676 if (isConfigLocked())
6677 {
6678 RTFileClose(mData->mHandleCfgFile);
6679 mData->mHandleCfgFile = NIL_RTFILE;
6680 }
6681
6682 LogFlowThisFunc (("\n"));
6683
6684 return rc;
6685}
6686
6687/**
6688 * Returns true if the settings file is located in the directory named exactly
6689 * as the machine. This will be true if the machine settings structure was
6690 * created by default in #openConfigLoader().
6691 *
6692 * @param aSettingsDir if not NULL, the full machine settings file directory
6693 * name will be assigned there.
6694 *
6695 * @note Doesn't lock anything.
6696 * @note Not thread safe (must be called from this object's lock).
6697 */
6698bool Machine::isInOwnDir (Utf8Str *aSettingsDir /* = NULL */)
6699{
6700 Utf8Str settingsDir = mData->mConfigFileFull;
6701 RTPathStripFilename (settingsDir.mutableRaw());
6702 char *dirName = RTPathFilename (settingsDir);
6703
6704 AssertReturn (dirName, false);
6705
6706 /* if we don't rename anything on name change, return false shorlty */
6707 if (!mUserData->mNameSync)
6708 return false;
6709
6710 if (aSettingsDir)
6711 *aSettingsDir = settingsDir;
6712
6713 return Bstr (dirName) == mUserData->mName;
6714}
6715
6716/**
6717 * @note Locks objects for reading!
6718 */
6719bool Machine::isModified()
6720{
6721 AutoCaller autoCaller (this);
6722 AssertComRCReturn (autoCaller.rc(), false);
6723
6724 AutoReaderLock alock (this);
6725
6726 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
6727 if (mNetworkAdapters [slot] && mNetworkAdapters [slot]->isModified())
6728 return true;
6729
6730 return
6731 mUserData.isBackedUp() ||
6732 mHWData.isBackedUp() ||
6733 mHDData.isBackedUp() ||
6734#ifdef VBOX_VRDP
6735 (mVRDPServer && mVRDPServer->isModified()) ||
6736#endif
6737 (mDVDDrive && mDVDDrive->isModified()) ||
6738 (mFloppyDrive && mFloppyDrive->isModified()) ||
6739 (mAudioAdapter && mAudioAdapter->isModified()) ||
6740 (mUSBController && mUSBController->isModified()) ||
6741 (mBIOSSettings && mBIOSSettings->isModified());
6742}
6743
6744/**
6745 * @note This method doesn't check (ignores) actual changes to mHDData.
6746 * Use mHDData.mHDAttachmentsChanged right after #commit() instead.
6747 *
6748 * @param aIgnoreUserData |true| to ignore changes to mUserData
6749 *
6750 * @note Locks objects for reading!
6751 */
6752bool Machine::isReallyModified (bool aIgnoreUserData /* = false */)
6753{
6754 AutoCaller autoCaller (this);
6755 AssertComRCReturn (autoCaller.rc(), false);
6756
6757 AutoReaderLock alock (this);
6758
6759 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
6760 if (mNetworkAdapters [slot] && mNetworkAdapters [slot]->isReallyModified())
6761 return true;
6762
6763 return
6764 (!aIgnoreUserData && mUserData.hasActualChanges()) ||
6765 mHWData.hasActualChanges() ||
6766 /* ignore mHDData */
6767 //mHDData.hasActualChanges() ||
6768#ifdef VBOX_VRDP
6769 (mVRDPServer && mVRDPServer->isReallyModified()) ||
6770#endif
6771 (mDVDDrive && mDVDDrive->isReallyModified()) ||
6772 (mFloppyDrive && mFloppyDrive->isReallyModified()) ||
6773 (mAudioAdapter && mAudioAdapter->isReallyModified()) ||
6774 (mUSBController && mUSBController->isReallyModified()) ||
6775 (mBIOSSettings && mBIOSSettings->isReallyModified());
6776}
6777
6778/**
6779 * Discards all changes to machine settings.
6780 *
6781 * @param aNotify whether to notify the direct session about changes or not
6782 *
6783 * @note Locks objects!
6784 */
6785void Machine::rollback (bool aNotify)
6786{
6787 AutoCaller autoCaller (this);
6788 AssertComRCReturn (autoCaller.rc(), (void) 0);
6789
6790 AutoLock alock (this);
6791
6792 mUserData.rollback();
6793
6794 mHWData.rollback();
6795
6796 if (mHDData.isBackedUp())
6797 fixupHardDisks (false /* aCommit */);
6798
6799 bool vrdpChanged = false, dvdChanged = false, floppyChanged = false,
6800 usbChanged = false;
6801 ComPtr <INetworkAdapter> networkAdapters [ELEMENTS (mNetworkAdapters)];
6802
6803 if (mBIOSSettings)
6804 mBIOSSettings->rollback();
6805
6806#ifdef VBOX_VRDP
6807 if (mVRDPServer)
6808 vrdpChanged = mVRDPServer->rollback();
6809#endif
6810
6811 if (mDVDDrive)
6812 dvdChanged = mDVDDrive->rollback();
6813
6814 if (mFloppyDrive)
6815 floppyChanged = mFloppyDrive->rollback();
6816
6817 if (mAudioAdapter)
6818 mAudioAdapter->rollback();
6819
6820 if (mUSBController)
6821 usbChanged = mUSBController->rollback();
6822
6823 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
6824 if (mNetworkAdapters [slot])
6825 if (mNetworkAdapters [slot]->rollback())
6826 networkAdapters [slot] = mNetworkAdapters [slot];
6827
6828 if (aNotify)
6829 {
6830 // inform the direct session about changes
6831
6832 ComObjPtr <Machine> that = this;
6833 alock.leave();
6834
6835 if (vrdpChanged)
6836 that->onVRDPServerChange();
6837 if (dvdChanged)
6838 that->onDVDDriveChange();
6839 if (floppyChanged)
6840 that->onFloppyDriveChange();
6841 if (usbChanged)
6842 that->onUSBControllerChange();
6843 for (ULONG slot = 0; slot < ELEMENTS (networkAdapters); slot ++)
6844 if (networkAdapters [slot])
6845 that->onNetworkAdapterChange (networkAdapters [slot]);
6846 }
6847}
6848
6849/**
6850 * Commits all the changes to machine settings.
6851 *
6852 * Note that when committing fails at some stage, it still continues
6853 * until the end. So, all data will either be actually committed or rolled
6854 * back (for failed cases) and the returned result code will describe the
6855 * first failure encountered. However, #isModified() will still return true
6856 * in case of failure, to indicade that settings in memory and on disk are
6857 * out of sync.
6858 *
6859 * @note Locks objects!
6860 */
6861HRESULT Machine::commit()
6862{
6863 AutoCaller autoCaller (this);
6864 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
6865
6866 AutoLock alock (this);
6867
6868 HRESULT rc = S_OK;
6869
6870 /*
6871 * use safe commit to ensure Snapshot machines (that share mUserData)
6872 * will still refer to a valid memory location
6873 */
6874 mUserData.commitCopy();
6875
6876 mHWData.commit();
6877
6878 if (mHDData.isBackedUp())
6879 rc = fixupHardDisks (true /* aCommit */);
6880
6881 mBIOSSettings->commit();
6882#ifdef VBOX_VRDP
6883 mVRDPServer->commit();
6884#endif
6885 mDVDDrive->commit();
6886 mFloppyDrive->commit();
6887 mAudioAdapter->commit();
6888 mUSBController->commit();
6889
6890 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
6891 mNetworkAdapters [slot]->commit();
6892
6893 if (mType == IsSessionMachine)
6894 {
6895 /* attach new data to the primary machine and reshare it */
6896 mPeer->mUserData.attach (mUserData);
6897 mPeer->mHWData.attach (mHWData);
6898 mPeer->mHDData.attach (mHDData);
6899 }
6900
6901 if (FAILED (rc))
6902 {
6903 /*
6904 * backup arbitrary data item to cause #isModified() to still return
6905 * true in case of any error
6906 */
6907 mHWData.backup();
6908 }
6909
6910 return rc;
6911}
6912
6913/**
6914 * Copies all the hardware data from the given machine.
6915 *
6916 * @note
6917 * This method must be called from under this object's lock.
6918 * @note
6919 * This method doesn't call #commit(), so all data remains backed up
6920 * and unsaved.
6921 */
6922void Machine::copyFrom (Machine *aThat)
6923{
6924 AssertReturn (mType == IsMachine || mType == IsSessionMachine, (void) 0);
6925 AssertReturn (aThat->mType == IsSnapshotMachine, (void) 0);
6926
6927 mHWData.assignCopy (aThat->mHWData);
6928
6929 // create copies of all shared folders (mHWData after attiching a copy
6930 // contains just references to original objects)
6931 for (HWData::SharedFolderList::iterator it = mHWData->mSharedFolders.begin();
6932 it != mHWData->mSharedFolders.end();
6933 ++ it)
6934 {
6935 ComObjPtr <SharedFolder> folder;
6936 folder.createObject();
6937 HRESULT rc = folder->initCopy (machine(), *it);
6938 AssertComRC (rc);
6939 *it = folder;
6940 }
6941
6942 mBIOSSettings->copyFrom (aThat->mBIOSSettings);
6943#ifdef VBOX_VRDP
6944 mVRDPServer->copyFrom (aThat->mVRDPServer);
6945#endif
6946 mDVDDrive->copyFrom (aThat->mDVDDrive);
6947 mFloppyDrive->copyFrom (aThat->mFloppyDrive);
6948 mAudioAdapter->copyFrom (aThat->mAudioAdapter);
6949 mUSBController->copyFrom (aThat->mUSBController);
6950
6951 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
6952 mNetworkAdapters [slot]->copyFrom (aThat->mNetworkAdapters [slot]);
6953}
6954
6955/////////////////////////////////////////////////////////////////////////////
6956// SessionMachine class
6957/////////////////////////////////////////////////////////////////////////////
6958
6959/** Task structure for asynchronous VM operations */
6960struct SessionMachine::Task
6961{
6962 Task (SessionMachine *m, Progress *p)
6963 : machine (m), progress (p)
6964 , state (m->data()->mMachineState) // save the current machine state
6965 , subTask (false), settingsChanged (false)
6966 {}
6967
6968 void modifyLastState (MachineState_T s)
6969 {
6970 *const_cast <MachineState_T *> (&state) = s;
6971 }
6972
6973 virtual void handler() = 0;
6974
6975 const ComObjPtr <SessionMachine> machine;
6976 const ComObjPtr <Progress> progress;
6977 const MachineState_T state;
6978
6979 bool subTask : 1;
6980 bool settingsChanged : 1;
6981};
6982
6983/** Take snapshot task */
6984struct SessionMachine::TakeSnapshotTask : public SessionMachine::Task
6985{
6986 TakeSnapshotTask (SessionMachine *m)
6987 : Task (m, NULL) {}
6988
6989 void handler() { machine->takeSnapshotHandler (*this); }
6990};
6991
6992/** Discard snapshot task */
6993struct SessionMachine::DiscardSnapshotTask : public SessionMachine::Task
6994{
6995 DiscardSnapshotTask (SessionMachine *m, Progress *p, Snapshot *s)
6996 : Task (m, p)
6997 , snapshot (s) {}
6998
6999 DiscardSnapshotTask (const Task &task, Snapshot *s)
7000 : Task (task)
7001 , snapshot (s) {}
7002
7003 void handler() { machine->discardSnapshotHandler (*this); }
7004
7005 const ComObjPtr <Snapshot> snapshot;
7006};
7007
7008/** Discard current state task */
7009struct SessionMachine::DiscardCurrentStateTask : public SessionMachine::Task
7010{
7011 DiscardCurrentStateTask (SessionMachine *m, Progress *p,
7012 bool discardCurSnapshot)
7013 : Task (m, p), discardCurrentSnapshot (discardCurSnapshot) {}
7014
7015 void handler() { machine->discardCurrentStateHandler (*this); }
7016
7017 const bool discardCurrentSnapshot;
7018};
7019
7020////////////////////////////////////////////////////////////////////////////////
7021
7022DEFINE_EMPTY_CTOR_DTOR (SessionMachine)
7023
7024HRESULT SessionMachine::FinalConstruct()
7025{
7026 LogFlowThisFunc (("\n"));
7027
7028 /* set the proper type to indicate we're the SessionMachine instance */
7029 unconst (mType) = IsSessionMachine;
7030
7031#if defined(__WIN__)
7032 mIPCSem = NULL;
7033#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
7034 mIPCSem = -1;
7035#endif
7036
7037 return S_OK;
7038}
7039
7040void SessionMachine::FinalRelease()
7041{
7042 LogFlowThisFunc (("\n"));
7043
7044 uninit (Uninit::Unexpected);
7045}
7046
7047/**
7048 * @note Must be called only by Machine::openSession() from its own write lock.
7049 */
7050HRESULT SessionMachine::init (Machine *aMachine)
7051{
7052 LogFlowThisFuncEnter();
7053 LogFlowThisFunc (("mName={%ls}\n", aMachine->mUserData->mName.raw()));
7054
7055 AssertReturn (aMachine, E_INVALIDARG);
7056
7057 AssertReturn (aMachine->lockHandle()->isLockedOnCurrentThread(), E_FAIL);
7058
7059 /* Enclose the state transition NotReady->InInit->Ready */
7060 AutoInitSpan autoInitSpan (this);
7061 AssertReturn (autoInitSpan.isOk(), E_UNEXPECTED);
7062
7063 /* create the interprocess semaphore */
7064#if defined(__WIN__)
7065 mIPCSemName = aMachine->mData->mConfigFileFull;
7066 for (size_t i = 0; i < mIPCSemName.length(); i++)
7067 if (mIPCSemName[i] == '\\')
7068 mIPCSemName[i] = '/';
7069 mIPCSem = ::CreateMutex (NULL, FALSE, mIPCSemName);
7070 ComAssertMsgRet (mIPCSem, ("Cannot create IPC mutex, err=0x%08X", ::GetLastError()),
7071 E_FAIL);
7072#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
7073 Utf8Str configFile = aMachine->mData->mConfigFileFull;
7074 char *pszConfigFile = NULL;
7075 RTStrUtf8ToCurrentCP (&pszConfigFile, configFile);
7076 key_t key = ::ftok (pszConfigFile, 0);
7077 RTStrFree (pszConfigFile);
7078 mIPCSem = ::semget (key, 1, S_IRWXU | S_IRWXG | S_IRWXO | IPC_CREAT);
7079 ComAssertMsgRet (mIPCSem >= 0, ("Cannot create IPC semaphore, errno=%d", errno),
7080 E_FAIL);
7081 /* set the initial value to 1 */
7082 int rv = ::semctl (mIPCSem, 0, SETVAL, 1);
7083 ComAssertMsgRet (rv == 0, ("Cannot init IPC semaphore, errno=%d", errno),
7084 E_FAIL);
7085#endif
7086
7087 /* memorize the peer Machine */
7088 unconst (mPeer) = aMachine;
7089 /* share the parent pointer */
7090 unconst (mParent) = aMachine->mParent;
7091
7092 /* take the pointers to data to share */
7093 mData.share (aMachine->mData);
7094 mSSData.share (aMachine->mSSData);
7095
7096 mUserData.share (aMachine->mUserData);
7097 mHWData.share (aMachine->mHWData);
7098 mHDData.share (aMachine->mHDData);
7099
7100 unconst (mBIOSSettings).createObject();
7101 mBIOSSettings->init (this, aMachine->mBIOSSettings);
7102#ifdef VBOX_VRDP
7103 /* create another VRDPServer object that will be mutable */
7104 unconst (mVRDPServer).createObject();
7105 mVRDPServer->init (this, aMachine->mVRDPServer);
7106#endif
7107 /* create another DVD drive object that will be mutable */
7108 unconst (mDVDDrive).createObject();
7109 mDVDDrive->init (this, aMachine->mDVDDrive);
7110 /* create another floppy drive object that will be mutable */
7111 unconst (mFloppyDrive).createObject();
7112 mFloppyDrive->init (this, aMachine->mFloppyDrive);
7113 /* create another audio adapter object that will be mutable */
7114 unconst (mAudioAdapter).createObject();
7115 mAudioAdapter->init (this, aMachine->mAudioAdapter);
7116 /* create another USB controller object that will be mutable */
7117 unconst (mUSBController).createObject();
7118 mUSBController->init (this, aMachine->mUSBController);
7119 /* create a list of network adapters that will be mutable */
7120 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
7121 {
7122 unconst (mNetworkAdapters [slot]).createObject();
7123 mNetworkAdapters [slot]->init (this, aMachine->mNetworkAdapters [slot]);
7124 }
7125
7126 /* Confirm a successful initialization when it's the case */
7127 autoInitSpan.setSucceeded();
7128
7129 LogFlowThisFuncLeave();
7130 return S_OK;
7131}
7132
7133/**
7134 * Uninitializes this session object. If the reason is other than
7135 * Uninit::Unexpected, then this method MUST be called from #checkForDeath().
7136 *
7137 * @param aReason uninitialization reason
7138 *
7139 * @note Locks mParent + this object for writing.
7140 */
7141void SessionMachine::uninit (Uninit::Reason aReason)
7142{
7143 LogFlowThisFuncEnter();
7144 LogFlowThisFunc (("reason=%d\n", aReason));
7145
7146 /*
7147 * Strongly reference ourselves to prevent this object deletion after
7148 * mData->mSession.mMachine.setNull() below (which can release the last
7149 * reference and call the destructor). Important: this must be done before
7150 * accessing any members (and before AutoUninitSpan that does it as well).
7151 * This self reference will be released as the very last step on return.
7152 */
7153 ComObjPtr <SessionMachine> selfRef = this;
7154
7155 /* Enclose the state transition Ready->InUninit->NotReady */
7156 AutoUninitSpan autoUninitSpan (this);
7157 if (autoUninitSpan.uninitDone())
7158 {
7159 LogFlowThisFunc (("Already uninitialized\n"));
7160 LogFlowThisFuncLeave();
7161 return;
7162 }
7163
7164 if (autoUninitSpan.initFailed())
7165 {
7166 /*
7167 * We've been called by init() because it's failed. It's not really
7168 * necessary (nor it's safe) to perform the regular uninit sequence
7169 * below, the following is enough.
7170 */
7171 LogFlowThisFunc (("Initialization failed\n"));
7172#if defined(__WIN__)
7173 if (mIPCSem)
7174 ::CloseHandle (mIPCSem);
7175 mIPCSem = NULL;
7176#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
7177 if (mIPCSem >= 0)
7178 ::semctl (mIPCSem, 0, IPC_RMID);
7179 mIPCSem = -1;
7180#endif
7181 uninitDataAndChildObjects();
7182 unconst (mParent).setNull();
7183 unconst (mPeer).setNull();
7184 LogFlowThisFuncLeave();
7185 return;
7186 }
7187
7188 /*
7189 * We need to lock this object in uninit() because the lock is shared
7190 * with mPeer (as well as data we modify below).
7191 * mParent->addProcessToReap() and others need mParent lock.
7192 */
7193 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
7194
7195 if (isModified())
7196 {
7197 LogWarningThisFunc (("Discarding unsaved settings changes!\n"));
7198 rollback (false /* aNotify */);
7199 }
7200
7201 Assert (!mSnapshotData.mStateFilePath || !mSnapshotData.mSnapshot);
7202 if (mSnapshotData.mStateFilePath)
7203 {
7204 LogWarningThisFunc (("canceling failed save state request!\n"));
7205 endSavingState (FALSE /* aSuccess */);
7206 }
7207 else if (!!mSnapshotData.mSnapshot)
7208 {
7209 LogWarningThisFunc (("canceling untaken snapshot!\n"));
7210 endTakingSnapshot (FALSE /* aSuccess */);
7211 }
7212
7213 /* release all captured USB devices */
7214 mParent->host()->releaseAllUSBDevices (this);
7215
7216 if (!mData->mSession.mType.isNull())
7217 {
7218 /* mType is not null when this machine's process has been started by
7219 * VirtualBox::OpenRemoteSession(), therefore it is our child. We
7220 * need to queue the PID to reap the process (and avoid zombies on
7221 * Linux). */
7222 Assert (mData->mSession.mPid != NIL_RTPROCESS);
7223 mParent->addProcessToReap (mData->mSession.mPid);
7224 }
7225
7226 mData->mSession.mPid = NIL_RTPROCESS;
7227
7228 if (aReason == Uninit::Unexpected)
7229 {
7230 /* Uninitialization didn't come from #checkForDeath(), so tell the
7231 * client watcher thread to update the set of machines that have open
7232 * sessions. */
7233 mParent->updateClientWatcher();
7234 }
7235
7236 /* uninitialize all remote controls */
7237 if (mData->mSession.mRemoteControls.size())
7238 {
7239 LogFlowThisFunc (("Closing remote sessions (%d):\n",
7240 mData->mSession.mRemoteControls.size()));
7241
7242 Data::Session::RemoteControlList::iterator it =
7243 mData->mSession.mRemoteControls.begin();
7244 while (it != mData->mSession.mRemoteControls.end())
7245 {
7246 LogFlowThisFunc ((" Calling remoteControl->Uninitialize()...\n"));
7247 HRESULT rc = (*it)->Uninitialize();
7248 LogFlowThisFunc ((" remoteControl->Uninitialize() returned %08X\n", rc));
7249 if (FAILED (rc))
7250 LogWarningThisFunc (("Forgot to close the remote session?\n"));
7251 ++ it;
7252 }
7253 mData->mSession.mRemoteControls.clear();
7254 }
7255
7256 /*
7257 * An expected uninitialization can come only from #checkForDeath().
7258 * Otherwise it means that something's got really wrong (for examlple,
7259 * the Session implementation has released the VirtualBox reference
7260 * before it triggered #OnSessionEnd(), or before releasing IPC semaphore,
7261 * etc). However, it's also possible, that the client releases the IPC
7262 * semaphore correctly (i.e. before it releases the VirtualBox reference),
7263 * but but the VirtualBox release event comes first to the server process.
7264 * This case is practically possible, so we should not assert on an
7265 * unexpected uninit, just log a warning.
7266 */
7267
7268 if ((aReason == Uninit::Unexpected))
7269 LogWarningThisFunc (("Unexpected SessionMachine uninitialization!\n"));
7270
7271 if (aReason != Uninit::Normal)
7272 mData->mSession.mDirectControl.setNull();
7273 else
7274 {
7275 /* this must be null here (see #OnSessionEnd()) */
7276 Assert (mData->mSession.mDirectControl.isNull());
7277 Assert (mData->mSession.mState == SessionState_SessionClosing);
7278 Assert (!mData->mSession.mProgress.isNull());
7279
7280 mData->mSession.mProgress->notifyComplete (S_OK);
7281 mData->mSession.mProgress.setNull();
7282 }
7283
7284 /* remove the association between the peer machine and this session machine */
7285 Assert (mData->mSession.mMachine == this ||
7286 aReason == Uninit::Unexpected);
7287
7288 /* reset the rest of session data */
7289 mData->mSession.mMachine.setNull();
7290 mData->mSession.mState = SessionState_SessionClosed;
7291 mData->mSession.mType.setNull();
7292
7293 /* close the interprocess semaphore before leaving the shared lock */
7294#if defined(__WIN__)
7295 if (mIPCSem)
7296 ::CloseHandle (mIPCSem);
7297 mIPCSem = NULL;
7298#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
7299 if (mIPCSem >= 0)
7300 ::semctl (mIPCSem, 0, IPC_RMID);
7301 mIPCSem = -1;
7302#endif
7303
7304 /* fire an event */
7305 mParent->onSessionStateChange (mData->mUuid, SessionState_SessionClosed);
7306
7307 uninitDataAndChildObjects();
7308
7309 /* leave the shared lock before setting the above two to NULL */
7310 alock.leave();
7311
7312 unconst (mParent).setNull();
7313 unconst (mPeer).setNull();
7314
7315 LogFlowThisFuncLeave();
7316}
7317
7318// AutoLock::Lockable interface
7319////////////////////////////////////////////////////////////////////////////////
7320
7321/**
7322 * Overrides VirtualBoxBase::lockHandle() in order to share the lock handle
7323 * with the primary Machine instance (mPeer).
7324 */
7325AutoLock::Handle *SessionMachine::lockHandle() const
7326{
7327 AssertReturn (!mPeer.isNull(), NULL);
7328 return mPeer->lockHandle();
7329}
7330
7331// IInternalMachineControl methods
7332////////////////////////////////////////////////////////////////////////////////
7333
7334/**
7335 * @note Locks the same as #setMachineState() does.
7336 */
7337STDMETHODIMP SessionMachine::UpdateState (MachineState_T machineState)
7338{
7339 return setMachineState (machineState);
7340}
7341
7342/**
7343 * @note Locks this object for reading.
7344 */
7345STDMETHODIMP SessionMachine::GetIPCId (BSTR *id)
7346{
7347 AutoCaller autoCaller (this);
7348 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7349
7350 AutoReaderLock alock (this);
7351
7352#if defined(__WIN__)
7353 mIPCSemName.cloneTo (id);
7354 return S_OK;
7355#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
7356 mData->mConfigFileFull.cloneTo (id);
7357 return S_OK;
7358#else
7359 return S_FAIL;
7360#endif
7361}
7362
7363/**
7364 * @note Locks this object for reading.
7365 */
7366STDMETHODIMP SessionMachine::GetLogFolder (BSTR *aLogFolder)
7367{
7368 AutoCaller autoCaller (this);
7369 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7370
7371 AutoReaderLock alock (this);
7372
7373 Utf8Str logFolder;
7374 getLogFolder (logFolder);
7375
7376 Bstr (logFolder).cloneTo (aLogFolder);
7377
7378 return S_OK;
7379}
7380
7381/**
7382 * Goes through the USB filters of the given machine to see if the given
7383 * device matches any filter or not.
7384 *
7385 * @note Locks the same as USBController::hasMatchingFilter() does.
7386 */
7387STDMETHODIMP SessionMachine::RunUSBDeviceFilters (IUSBDevice *aUSBDevice,
7388 BOOL *aMatched)
7389{
7390 if (!aUSBDevice)
7391 return E_INVALIDARG;
7392 if (!aMatched)
7393 return E_POINTER;
7394
7395 AutoCaller autoCaller (this);
7396 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7397
7398 *aMatched = mUSBController->hasMatchingFilter (aUSBDevice);
7399
7400 return S_OK;
7401}
7402
7403/**
7404 * @note Locks the same as Host::captureUSBDevice() does.
7405 */
7406STDMETHODIMP SessionMachine::CaptureUSBDevice (INPTR GUIDPARAM aId,
7407 IUSBDevice **aHostDevice)
7408{
7409 if (!aHostDevice)
7410 return E_POINTER;
7411
7412 AutoCaller autoCaller (this);
7413 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7414
7415 // if cautureUSBDevice() fails, it must have set extended error info
7416 return mParent->host()->captureUSBDevice (this, aId, aHostDevice);
7417}
7418
7419/**
7420 * @note Locks the same as Host::releaseUSBDevice() does.
7421 */
7422STDMETHODIMP SessionMachine::ReleaseUSBDevice (INPTR GUIDPARAM aId)
7423{
7424 AutoCaller autoCaller (this);
7425 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7426
7427 return mParent->host()->releaseUSBDevice (this, aId);
7428}
7429
7430/**
7431 * @note Locks the same as Host::autoCaptureUSBDevices() does.
7432 */
7433STDMETHODIMP SessionMachine::AutoCaptureUSBDevices (IUSBDeviceCollection **aHostDevices)
7434{
7435 AutoCaller autoCaller (this);
7436 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7437
7438 return mParent->host()->autoCaptureUSBDevices (this, aHostDevices);
7439}
7440
7441/**
7442 * @note Locks the same as Host::releaseAllUSBDevices() does.
7443 */
7444STDMETHODIMP SessionMachine::ReleaseAllUSBDevices()
7445{
7446 AutoCaller autoCaller (this);
7447 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7448
7449 return mParent->host()->releaseAllUSBDevices (this);
7450}
7451
7452/**
7453 * @note Locks mParent + this object for writing.
7454 */
7455STDMETHODIMP SessionMachine::OnSessionEnd (ISession *aSession,
7456 IProgress **aProgress)
7457{
7458 LogFlowThisFuncEnter();
7459
7460 AssertReturn (aSession, E_INVALIDARG);
7461 AssertReturn (aProgress, E_INVALIDARG);
7462
7463 AutoCaller autoCaller (this);
7464
7465 LogFlowThisFunc (("state=%d\n", autoCaller.state()));
7466 /*
7467 * We don't assert below because it might happen that a non-direct session
7468 * informs us it is closed right after we've been uninitialized -- it's ok.
7469 */
7470 CheckComRCReturnRC (autoCaller.rc());
7471
7472 /* get IInternalSessionControl interface */
7473 ComPtr <IInternalSessionControl> control (aSession);
7474
7475 ComAssertRet (!control.isNull(), E_INVALIDARG);
7476
7477 /* Progress::init() needs mParent lock */
7478 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
7479
7480 if (control.equalsTo (mData->mSession.mDirectControl))
7481 {
7482 ComAssertRet (aProgress, E_POINTER);
7483
7484 /* The direct session is being normally closed by the client process
7485 * ----------------------------------------------------------------- */
7486
7487 /* go to the closing state (essential for all open*Session() calls and
7488 * for #checkForDeath()) */
7489 Assert (mData->mSession.mState == SessionState_SessionOpen);
7490 mData->mSession.mState = SessionState_SessionClosing;
7491
7492 /* set direct control to NULL to release the remote instance */
7493 mData->mSession.mDirectControl.setNull();
7494 LogFlowThisFunc (("Direct control is set to NULL\n"));
7495
7496 /*
7497 * Create the progress object the client will use to wait until
7498 * #checkForDeath() is called to uninitialize this session object
7499 * after it releases the IPC semaphore.
7500 */
7501 ComObjPtr <Progress> progress;
7502 progress.createObject();
7503 progress->init (mParent, (IMachine *) mPeer, Bstr (tr ("Closing session")),
7504 FALSE /* aCancelable */);
7505 progress.queryInterfaceTo (aProgress);
7506 mData->mSession.mProgress = progress;
7507 }
7508 else
7509 {
7510 /* the remote session is being normally closed */
7511 Data::Session::RemoteControlList::iterator it =
7512 mData->mSession.mRemoteControls.begin();
7513 while (it != mData->mSession.mRemoteControls.end())
7514 {
7515 if (control.equalsTo (*it))
7516 break;
7517 ++it;
7518 }
7519 BOOL found = it != mData->mSession.mRemoteControls.end();
7520 ComAssertMsgRet (found, ("The session is not found in the session list!"),
7521 E_INVALIDARG);
7522 mData->mSession.mRemoteControls.remove (*it);
7523 }
7524
7525 LogFlowThisFuncLeave();
7526 return S_OK;
7527}
7528
7529/**
7530 * @note Locks mParent + this object for writing.
7531 */
7532STDMETHODIMP SessionMachine::BeginSavingState (IProgress *aProgress, BSTR *aStateFilePath)
7533{
7534 LogFlowThisFuncEnter();
7535
7536 AssertReturn (aProgress, E_INVALIDARG);
7537 AssertReturn (aStateFilePath, E_POINTER);
7538
7539 AutoCaller autoCaller (this);
7540 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7541
7542 /* mParent->addProgress() needs mParent lock */
7543 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
7544
7545 AssertReturn (mData->mMachineState == MachineState_Paused &&
7546 mSnapshotData.mLastState == MachineState_InvalidMachineState &&
7547 mSnapshotData.mProgressId.isEmpty() &&
7548 mSnapshotData.mStateFilePath.isNull(),
7549 E_FAIL);
7550
7551 /* memorize the progress ID and add it to the global collection */
7552 Guid progressId;
7553 HRESULT rc = aProgress->COMGETTER(Id) (progressId.asOutParam());
7554 AssertComRCReturn (rc, rc);
7555 rc = mParent->addProgress (aProgress);
7556 AssertComRCReturn (rc, rc);
7557
7558 Bstr stateFilePath;
7559 /* stateFilePath is null when the machine is not running */
7560 if (mData->mMachineState == MachineState_Paused)
7561 {
7562 stateFilePath = Utf8StrFmt ("%ls%c{%Vuuid}.sav",
7563 mUserData->mSnapshotFolderFull.raw(),
7564 RTPATH_DELIMITER, mData->mUuid.raw());
7565 }
7566
7567 /* fill in the snapshot data */
7568 mSnapshotData.mLastState = mData->mMachineState;
7569 mSnapshotData.mProgressId = progressId;
7570 mSnapshotData.mStateFilePath = stateFilePath;
7571
7572 /* set the state to Saving (this is expected by Console::SaveState()) */
7573 setMachineState (MachineState_Saving);
7574
7575 stateFilePath.cloneTo (aStateFilePath);
7576
7577 return S_OK;
7578}
7579
7580/**
7581 * @note Locks mParent + this objects for writing.
7582 */
7583STDMETHODIMP SessionMachine::EndSavingState (BOOL aSuccess)
7584{
7585 LogFlowThisFunc (("\n"));
7586
7587 AutoCaller autoCaller (this);
7588 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7589
7590 /* endSavingState() need mParent lock */
7591 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
7592
7593 AssertReturn (mData->mMachineState == MachineState_Saving &&
7594 mSnapshotData.mLastState != MachineState_InvalidMachineState &&
7595 !mSnapshotData.mProgressId.isEmpty() &&
7596 !mSnapshotData.mStateFilePath.isNull(),
7597 E_FAIL);
7598
7599 /*
7600 * on success, set the state to Saved;
7601 * on failure, set the state to the state we had when BeginSavingState() was
7602 * called (this is expected by Console::SaveState() and
7603 * Console::saveStateThread())
7604 */
7605 if (aSuccess)
7606 setMachineState (MachineState_Saved);
7607 else
7608 setMachineState (mSnapshotData.mLastState);
7609
7610 return endSavingState (aSuccess);
7611}
7612
7613/**
7614 * @note Locks mParent + this objects for writing.
7615 */
7616STDMETHODIMP SessionMachine::BeginTakingSnapshot (
7617 IConsole *aInitiator, INPTR BSTR aName, INPTR BSTR aDescription,
7618 IProgress *aProgress, BSTR *aStateFilePath,
7619 IProgress **aServerProgress)
7620{
7621 LogFlowThisFuncEnter();
7622
7623 AssertReturn (aInitiator && aName, E_INVALIDARG);
7624 AssertReturn (aStateFilePath && aServerProgress, E_POINTER);
7625
7626 LogFlowThisFunc (("aName='%ls'\n", aName));
7627
7628 AutoCaller autoCaller (this);
7629 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7630
7631 /* Progress::init() needs mParent lock */
7632 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
7633
7634 AssertReturn ((mData->mMachineState < MachineState_Running ||
7635 mData->mMachineState == MachineState_Paused) &&
7636 mSnapshotData.mLastState == MachineState_InvalidMachineState &&
7637 mSnapshotData.mSnapshot.isNull() &&
7638 mSnapshotData.mServerProgress.isNull() &&
7639 mSnapshotData.mCombinedProgress.isNull(),
7640 E_FAIL);
7641
7642 bool takingSnapshotOnline = mData->mMachineState == MachineState_Paused;
7643
7644 if (!takingSnapshotOnline && mData->mMachineState != MachineState_Saved)
7645 {
7646 /*
7647 * save all current settings to ensure current changes are committed
7648 * and hard disks are fixed up
7649 */
7650 HRESULT rc = saveSettings();
7651 CheckComRCReturnRC (rc);
7652 }
7653
7654 /* check that there are no Writethrough hard disks attached */
7655 for (HDData::HDAttachmentList::const_iterator
7656 it = mHDData->mHDAttachments.begin();
7657 it != mHDData->mHDAttachments.end();
7658 ++ it)
7659 {
7660 ComObjPtr <HardDisk> hd = (*it)->hardDisk();
7661 AutoLock hdLock (hd);
7662 if (hd->type() == HardDiskType_WritethroughHardDisk)
7663 return setError (E_FAIL,
7664 tr ("Cannot take a snapshot when there is a Writethrough hard "
7665 " disk attached ('%ls')"), hd->toString().raw());
7666 }
7667
7668 AssertReturn (aProgress || !takingSnapshotOnline, E_FAIL);
7669
7670 /* create an ID for the snapshot */
7671 Guid snapshotId;
7672 snapshotId.create();
7673
7674 Bstr stateFilePath;
7675 /* stateFilePath is null when the machine is not online nor saved */
7676 if (takingSnapshotOnline || mData->mMachineState == MachineState_Saved)
7677 stateFilePath = Utf8StrFmt ("%ls%c{%Vuuid}.sav",
7678 mUserData->mSnapshotFolderFull.raw(),
7679 RTPATH_DELIMITER,
7680 snapshotId.ptr());
7681
7682 /* ensure the directory for the saved state file exists */
7683 if (stateFilePath)
7684 {
7685 Utf8Str dir = stateFilePath;
7686 RTPathStripFilename (dir.mutableRaw());
7687 if (!RTDirExists (dir))
7688 {
7689 int vrc = RTDirCreateFullPath (dir, 0777);
7690 if (VBOX_FAILURE (vrc))
7691 return setError (E_FAIL,
7692 tr ("Could not create a directory '%s' to save the "
7693 "VM state to (%Vrc)"),
7694 dir.raw(), vrc);
7695 }
7696 }
7697
7698 /* create a snapshot machine object */
7699 ComObjPtr <SnapshotMachine> snapshotMachine;
7700 snapshotMachine.createObject();
7701 HRESULT rc = snapshotMachine->init (this, snapshotId, stateFilePath);
7702 AssertComRCReturn (rc, rc);
7703
7704 Bstr progressDesc = Bstr (tr ("Taking snapshot of virtual machine"));
7705 Bstr firstOpDesc = Bstr (tr ("Preparing to take snapshot"));
7706
7707 /*
7708 * create a server-side progress object (it will be descriptionless
7709 * when we need to combine it with the VM-side progress, i.e. when we're
7710 * taking a snapshot online). The number of operations is:
7711 * 1 (preparing) + # of VDIs + 1 (if the state is saved so we need to copy it)
7712 */
7713 ComObjPtr <Progress> serverProgress;
7714 {
7715 ULONG opCount = 1 + mHDData->mHDAttachments.size();
7716 if (mData->mMachineState == MachineState_Saved)
7717 opCount ++;
7718 serverProgress.createObject();
7719 if (takingSnapshotOnline)
7720 rc = serverProgress->init (FALSE, opCount, firstOpDesc);
7721 else
7722 rc = serverProgress->init (mParent, aInitiator, progressDesc, FALSE,
7723 opCount, firstOpDesc);
7724 AssertComRCReturn (rc, rc);
7725 }
7726
7727 /* create a combined server-side progress object when necessary */
7728 ComObjPtr <CombinedProgress> combinedProgress;
7729 if (takingSnapshotOnline)
7730 {
7731 combinedProgress.createObject();
7732 rc = combinedProgress->init (mParent, aInitiator, progressDesc,
7733 serverProgress, aProgress);
7734 AssertComRCReturn (rc, rc);
7735 }
7736
7737 /* create a snapshot object */
7738 RTTIMESPEC time;
7739 ComObjPtr <Snapshot> snapshot;
7740 snapshot.createObject();
7741 rc = snapshot->init (snapshotId, aName, aDescription,
7742 RTTimeSpecGetMilli (RTTimeNow (&time)),
7743 snapshotMachine, mData->mCurrentSnapshot);
7744 AssertComRCReturn (rc, rc);
7745
7746 /*
7747 * create and start the task on a separate thread
7748 * (note that it will not start working until we release alock)
7749 */
7750 TakeSnapshotTask *task = new TakeSnapshotTask (this);
7751 int vrc = RTThreadCreate (NULL, taskHandler,
7752 (void *) task,
7753 0, RTTHREADTYPE_MAIN_WORKER, 0, "TakeSnapshot");
7754 if (VBOX_FAILURE (vrc))
7755 {
7756 snapshot->uninit();
7757 delete task;
7758 ComAssertFailedRet (E_FAIL);
7759 }
7760
7761 /* fill in the snapshot data */
7762 mSnapshotData.mLastState = mData->mMachineState;
7763 mSnapshotData.mSnapshot = snapshot;
7764 mSnapshotData.mServerProgress = serverProgress;
7765 mSnapshotData.mCombinedProgress = combinedProgress;
7766
7767 /* set the state to Saving (this is expected by Console::TakeSnapshot()) */
7768 setMachineState (MachineState_Saving);
7769
7770 if (takingSnapshotOnline)
7771 stateFilePath.cloneTo (aStateFilePath);
7772 else
7773 *aStateFilePath = NULL;
7774
7775 serverProgress.queryInterfaceTo (aServerProgress);
7776
7777 LogFlowThisFuncLeave();
7778 return S_OK;
7779}
7780
7781/**
7782 * @note Locks mParent + this objects for writing.
7783 */
7784STDMETHODIMP SessionMachine::EndTakingSnapshot (BOOL aSuccess)
7785{
7786 LogFlowThisFunc (("\n"));
7787
7788 AutoCaller autoCaller (this);
7789 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7790
7791 /* Lock mParent because of endTakingSnapshot() */
7792 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
7793
7794 AssertReturn (!aSuccess ||
7795 (mData->mMachineState == MachineState_Saving &&
7796 mSnapshotData.mLastState != MachineState_InvalidMachineState &&
7797 !mSnapshotData.mSnapshot.isNull() &&
7798 !mSnapshotData.mServerProgress.isNull() &&
7799 !mSnapshotData.mCombinedProgress.isNull()),
7800 E_FAIL);
7801
7802 /*
7803 * set the state to the state we had when BeginTakingSnapshot() was called
7804 * (this is expected by Console::TakeSnapshot() and
7805 * Console::saveStateThread())
7806 */
7807 setMachineState (mSnapshotData.mLastState);
7808
7809 return endTakingSnapshot (aSuccess);
7810}
7811
7812/**
7813 * @note Locks mParent + this + children objects for writing!
7814 */
7815STDMETHODIMP SessionMachine::DiscardSnapshot (
7816 IConsole *aInitiator, INPTR GUIDPARAM aId,
7817 MachineState_T *aMachineState, IProgress **aProgress)
7818{
7819 LogFlowThisFunc (("\n"));
7820
7821 Guid id = aId;
7822 AssertReturn (aInitiator && !id.isEmpty(), E_INVALIDARG);
7823 AssertReturn (aMachineState && aProgress, E_POINTER);
7824
7825 AutoCaller autoCaller (this);
7826 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7827
7828 /* Progress::init() needs mParent lock */
7829 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
7830
7831 ComAssertRet (mData->mMachineState < MachineState_Running, E_FAIL);
7832
7833 ComObjPtr <Snapshot> snapshot;
7834 HRESULT rc = findSnapshot (id, snapshot, true /* aSetError */);
7835 CheckComRCReturnRC (rc);
7836
7837 AutoLock snapshotLock (snapshot);
7838 if (snapshot == mData->mFirstSnapshot)
7839 {
7840 AutoLock chLock (mData->mFirstSnapshot->childrenLock());
7841 size_t childrenCount = mData->mFirstSnapshot->children().size();
7842 if (childrenCount > 1)
7843 return setError (E_FAIL,
7844 tr ("Cannot discard the snapshot '%ls' because it is the first "
7845 "snapshot of the machine '%ls' and it has more than one "
7846 "child snapshot (%d)"),
7847 snapshot->data().mName.raw(), mUserData->mName.raw(),
7848 childrenCount);
7849 }
7850
7851 /*
7852 * If the snapshot being discarded is the current one, ensure current
7853 * settings are committed and saved.
7854 */
7855 if (snapshot == mData->mCurrentSnapshot)
7856 {
7857 if (isModified())
7858 {
7859 rc = saveSettings();
7860 CheckComRCReturnRC (rc);
7861 }
7862 }
7863
7864 /*
7865 * create a progress object. The number of operations is:
7866 * 1 (preparing) + # of VDIs
7867 */
7868 ComObjPtr <Progress> progress;
7869 progress.createObject();
7870 rc = progress->init (mParent, aInitiator,
7871 Bstr (Utf8StrFmt (tr ("Discarding snapshot '%ls'"),
7872 snapshot->data().mName.raw())),
7873 FALSE /* aCancelable */,
7874 1 + snapshot->data().mMachine->mHDData->mHDAttachments.size(),
7875 Bstr (tr ("Preparing to discard snapshot")));
7876 AssertComRCReturn (rc, rc);
7877
7878 /* create and start the task on a separate thread */
7879 DiscardSnapshotTask *task = new DiscardSnapshotTask (this, progress, snapshot);
7880 int vrc = RTThreadCreate (NULL, taskHandler,
7881 (void *) task,
7882 0, RTTHREADTYPE_MAIN_WORKER, 0, "DiscardSnapshot");
7883 if (VBOX_FAILURE (vrc))
7884 delete task;
7885 ComAssertRCRet (vrc, E_FAIL);
7886
7887 /* set the proper machine state (note: after creating a Task instance) */
7888 setMachineState (MachineState_Discarding);
7889
7890 /* return the progress to the caller */
7891 progress.queryInterfaceTo (aProgress);
7892
7893 /* return the new state to the caller */
7894 *aMachineState = mData->mMachineState;
7895
7896 return S_OK;
7897}
7898
7899/**
7900 * @note Locks mParent + this + children objects for writing!
7901 */
7902STDMETHODIMP SessionMachine::DiscardCurrentState (
7903 IConsole *aInitiator, MachineState_T *aMachineState, IProgress **aProgress)
7904{
7905 LogFlowThisFunc (("\n"));
7906
7907 AssertReturn (aInitiator, E_INVALIDARG);
7908 AssertReturn (aMachineState && aProgress, E_POINTER);
7909
7910 AutoCaller autoCaller (this);
7911 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7912
7913 /* Progress::init() needs mParent lock */
7914 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
7915
7916 ComAssertRet (mData->mMachineState < MachineState_Running, E_FAIL);
7917
7918 if (mData->mCurrentSnapshot.isNull())
7919 return setError (E_FAIL,
7920 tr ("Could not discard the current state of the machine '%ls' "
7921 "because it doesn't have any snapshots"),
7922 mUserData->mName.raw());
7923
7924 /*
7925 * create a progress object. The number of operations is:
7926 * 1 (preparing) + # of VDIs + 1 (if we need to copy the saved state file)
7927 */
7928 ComObjPtr <Progress> progress;
7929 progress.createObject();
7930 {
7931 ULONG opCount = 1 + mData->mCurrentSnapshot->data()
7932 .mMachine->mHDData->mHDAttachments.size();
7933 if (mData->mCurrentSnapshot->stateFilePath())
7934 ++ opCount;
7935 progress->init (mParent, aInitiator,
7936 Bstr (tr ("Discarding current machine state")),
7937 FALSE /* aCancelable */, opCount,
7938 Bstr (tr ("Preparing to discard current state")));
7939 }
7940
7941 /* create and start the task on a separate thread */
7942 DiscardCurrentStateTask *task =
7943 new DiscardCurrentStateTask (this, progress, false /* discardCurSnapshot */);
7944 int vrc = RTThreadCreate (NULL, taskHandler,
7945 (void *) task,
7946 0, RTTHREADTYPE_MAIN_WORKER, 0, "DiscardCurState");
7947 if (VBOX_FAILURE (vrc))
7948 delete task;
7949 ComAssertRCRet (vrc, E_FAIL);
7950
7951 /* set the proper machine state (note: after creating a Task instance) */
7952 setMachineState (MachineState_Discarding);
7953
7954 /* return the progress to the caller */
7955 progress.queryInterfaceTo (aProgress);
7956
7957 /* return the new state to the caller */
7958 *aMachineState = mData->mMachineState;
7959
7960 return S_OK;
7961}
7962
7963/**
7964 * @note Locks mParent + other objects for writing!
7965 */
7966STDMETHODIMP SessionMachine::DiscardCurrentSnapshotAndState (
7967 IConsole *aInitiator, MachineState_T *aMachineState, IProgress **aProgress)
7968{
7969 LogFlowThisFunc (("\n"));
7970
7971 AssertReturn (aInitiator, E_INVALIDARG);
7972 AssertReturn (aMachineState && aProgress, E_POINTER);
7973
7974 AutoCaller autoCaller (this);
7975 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7976
7977 /* Progress::init() needs mParent lock */
7978 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
7979
7980 ComAssertRet (mData->mMachineState < MachineState_Running, E_FAIL);
7981
7982 if (mData->mCurrentSnapshot.isNull())
7983 return setError (E_FAIL,
7984 tr ("Could not discard the current state of the machine '%ls' "
7985 "because it doesn't have any snapshots"),
7986 mUserData->mName.raw());
7987
7988 /*
7989 * create a progress object. The number of operations is:
7990 * 1 (preparing) + # of VDIs in the current snapshot +
7991 * # of VDIs in the previous snapshot +
7992 * 1 (if we need to copy the saved state file of the previous snapshot)
7993 * or (if there is no previous snapshot):
7994 * 1 (preparing) + # of VDIs in the current snapshot * 2 +
7995 * 1 (if we need to copy the saved state file of the current snapshot)
7996 */
7997 ComObjPtr <Progress> progress;
7998 progress.createObject();
7999 {
8000 ComObjPtr <Snapshot> curSnapshot = mData->mCurrentSnapshot;
8001 ComObjPtr <Snapshot> prevSnapshot = mData->mCurrentSnapshot->parent();
8002
8003 ULONG opCount = 1;
8004 if (prevSnapshot)
8005 {
8006 opCount += curSnapshot->data().mMachine->mHDData->mHDAttachments.size();
8007 opCount += prevSnapshot->data().mMachine->mHDData->mHDAttachments.size();
8008 if (prevSnapshot->stateFilePath())
8009 ++ opCount;
8010 }
8011 else
8012 {
8013 opCount += curSnapshot->data().mMachine->mHDData->mHDAttachments.size() * 2;
8014 if (curSnapshot->stateFilePath())
8015 ++ opCount;
8016 }
8017
8018 progress->init (mParent, aInitiator,
8019 Bstr (tr ("Discarding current machine snapshot and state")),
8020 FALSE /* aCancelable */, opCount,
8021 Bstr (tr ("Preparing to discard current snapshot and state")));
8022 }
8023
8024 /* create and start the task on a separate thread */
8025 DiscardCurrentStateTask *task =
8026 new DiscardCurrentStateTask (this, progress, true /* discardCurSnapshot */);
8027 int vrc = RTThreadCreate (NULL, taskHandler,
8028 (void *) task,
8029 0, RTTHREADTYPE_MAIN_WORKER, 0, "DiscardCurState");
8030 if (VBOX_FAILURE (vrc))
8031 delete task;
8032 ComAssertRCRet (vrc, E_FAIL);
8033
8034 /* set the proper machine state (note: after creating a Task instance) */
8035 setMachineState (MachineState_Discarding);
8036
8037 /* return the progress to the caller */
8038 progress.queryInterfaceTo (aProgress);
8039
8040 /* return the new state to the caller */
8041 *aMachineState = mData->mMachineState;
8042
8043 return S_OK;
8044}
8045
8046// public methods only for internal purposes
8047/////////////////////////////////////////////////////////////////////////////
8048
8049/**
8050 * Called from the client watcher thread to check for unexpected client
8051 * process death.
8052 *
8053 * @note On Win32, this method is called only when we've got the semaphore
8054 * (i.e. it has been signaled when we were waiting for it).
8055 *
8056 * On Win32, this method always returns true.
8057 *
8058 * On Linux, the method returns true if the client process has terminated
8059 * abnormally (and/or the session has been uninitialized) and false if it is
8060 * still alive.
8061 *
8062 * @note Locks this object for writing.
8063 */
8064bool SessionMachine::checkForDeath()
8065{
8066 Uninit::Reason reason;
8067 bool doUninit = false;
8068 bool rc = false;
8069
8070 /*
8071 * Enclose autoCaller with a block because calling uninit()
8072 * from under it will deadlock.
8073 */
8074 {
8075 AutoCaller autoCaller (this);
8076 if (!autoCaller.isOk())
8077 {
8078 /*
8079 * return true if not ready, to cause the client watcher to exclude
8080 * the corresponding session from watching
8081 */
8082 LogFlowThisFunc (("Already uninitialized!"));
8083 return true;
8084 }
8085
8086 AutoLock alock (this);
8087
8088 /*
8089 * Determine the reason of death: if the session state is Closing here,
8090 * everything is fine. Otherwise it means that the client did not call
8091 * OnSessionEnd() before it released the IPC semaphore.
8092 * This may happen either because the client process has abnormally
8093 * terminated, or because it simply forgot to call ISession::Close()
8094 * before exiting. We threat the latter also as an abnormal termination
8095 * (see Session::uninit() for details).
8096 */
8097 reason = mData->mSession.mState == SessionState_SessionClosing ?
8098 Uninit::Normal :
8099 Uninit::Abnormal;
8100
8101#if defined(__WIN__)
8102
8103 AssertMsg (mIPCSem, ("semaphore must be created"));
8104
8105 if (reason == Uninit::Abnormal)
8106 {
8107 LogWarningThisFunc (("ABNORMAL client termination! (wasRunning=%d)\n",
8108 mData->mMachineState >= MachineState_Running));
8109
8110 /* reset the state to Aborted */
8111 if (mData->mMachineState != MachineState_Aborted)
8112 setMachineState (MachineState_Aborted);
8113 }
8114
8115 /* release the IPC mutex */
8116 ::ReleaseMutex (mIPCSem);
8117
8118 doUninit = true;
8119
8120 rc = true;
8121
8122#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
8123
8124 AssertMsg (mIPCSem >= 0, ("semaphore must be created"));
8125
8126 int val = ::semctl (mIPCSem, 0, GETVAL);
8127 if (val > 0)
8128 {
8129 /* the semaphore is signaled, meaning the session is terminated */
8130
8131 if (reason == Uninit::Abnormal)
8132 {
8133 LogWarningThisFunc (("ABNORMAL client termination! (wasRunning=%d)\n",
8134 mData->mMachineState >= MachineState_Running));
8135
8136 /* reset the state to Aborted */
8137 if (mData->mMachineState != MachineState_Aborted)
8138 setMachineState (MachineState_Aborted);
8139 }
8140
8141 doUninit = true;
8142 }
8143
8144 rc = val > 0;
8145
8146#endif
8147
8148 } /* AutoCaller block */
8149
8150 if (doUninit)
8151 uninit (reason);
8152
8153 return rc;
8154}
8155
8156/**
8157 * @note Locks this object for reading.
8158 */
8159HRESULT SessionMachine::onDVDDriveChange()
8160{
8161 LogFlowThisFunc (("\n"));
8162
8163 AutoCaller autoCaller (this);
8164 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8165
8166 ComPtr <IInternalSessionControl> directControl;
8167 {
8168 AutoReaderLock alock (this);
8169 directControl = mData->mSession.mDirectControl;
8170 }
8171
8172 /* ignore notifications sent after #OnSessionEnd() is called */
8173 if (!directControl)
8174 return S_OK;
8175
8176 return directControl->OnDVDDriveChange();
8177}
8178
8179/**
8180 * @note Locks this object for reading.
8181 */
8182HRESULT SessionMachine::onFloppyDriveChange()
8183{
8184 LogFlowThisFunc (("\n"));
8185
8186 AutoCaller autoCaller (this);
8187 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8188
8189 ComPtr <IInternalSessionControl> directControl;
8190 {
8191 AutoReaderLock alock (this);
8192 directControl = mData->mSession.mDirectControl;
8193 }
8194
8195 /* ignore notifications sent after #OnSessionEnd() is called */
8196 if (!directControl)
8197 return S_OK;
8198
8199 return directControl->OnFloppyDriveChange();
8200}
8201
8202/**
8203 * @note Locks this object for reading.
8204 */
8205HRESULT SessionMachine::onNetworkAdapterChange(INetworkAdapter *networkAdapter)
8206{
8207 LogFlowThisFunc (("\n"));
8208
8209 AutoCaller autoCaller (this);
8210 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8211
8212 ComPtr <IInternalSessionControl> directControl;
8213 {
8214 AutoReaderLock alock (this);
8215 directControl = mData->mSession.mDirectControl;
8216 }
8217
8218 /* ignore notifications sent after #OnSessionEnd() is called */
8219 if (!directControl)
8220 return S_OK;
8221
8222 return directControl->OnNetworkAdapterChange(networkAdapter);
8223}
8224
8225/**
8226 * @note Locks this object for reading.
8227 */
8228HRESULT SessionMachine::onVRDPServerChange()
8229{
8230 LogFlowThisFunc (("\n"));
8231
8232 AutoCaller autoCaller (this);
8233 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8234
8235 ComPtr <IInternalSessionControl> directControl;
8236 {
8237 AutoReaderLock alock (this);
8238 directControl = mData->mSession.mDirectControl;
8239 }
8240
8241 /* ignore notifications sent after #OnSessionEnd() is called */
8242 if (!directControl)
8243 return S_OK;
8244
8245 return directControl->OnVRDPServerChange();
8246}
8247
8248/**
8249 * @note Locks this object for reading.
8250 */
8251HRESULT SessionMachine::onUSBControllerChange()
8252{
8253 LogFlowThisFunc (("\n"));
8254
8255 AutoCaller autoCaller (this);
8256 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8257
8258 ComPtr <IInternalSessionControl> directControl;
8259 {
8260 AutoReaderLock alock (this);
8261 directControl = mData->mSession.mDirectControl;
8262 }
8263
8264 /* ignore notifications sent after #OnSessionEnd() is called */
8265 if (!directControl)
8266 return S_OK;
8267
8268 return directControl->OnUSBControllerChange();
8269}
8270
8271/**
8272 * @note Locks this object for reading.
8273 */
8274HRESULT SessionMachine::onUSBDeviceAttach (IUSBDevice *aDevice)
8275{
8276 LogFlowThisFunc (("\n"));
8277
8278 AutoCaller autoCaller (this);
8279 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8280
8281 ComPtr <IInternalSessionControl> directControl;
8282 {
8283 AutoReaderLock alock (this);
8284 directControl = mData->mSession.mDirectControl;
8285 }
8286
8287 /* ignore notifications sent after #OnSessionEnd() is called */
8288 if (!directControl)
8289 return S_OK;
8290
8291 return directControl->OnUSBDeviceAttach (aDevice);
8292}
8293
8294/**
8295 * @note Locks this object for reading.
8296 */
8297HRESULT SessionMachine::onUSBDeviceDetach (INPTR GUIDPARAM aId)
8298{
8299 LogFlowThisFunc (("\n"));
8300
8301 AutoCaller autoCaller (this);
8302 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8303
8304 ComPtr <IInternalSessionControl> directControl;
8305 {
8306 AutoReaderLock alock (this);
8307 directControl = mData->mSession.mDirectControl;
8308 }
8309
8310 /* ignore notifications sent after #OnSessionEnd() is called */
8311 if (!directControl)
8312 return S_OK;
8313
8314 return directControl->OnUSBDeviceDetach (aId);
8315}
8316
8317// protected methods
8318/////////////////////////////////////////////////////////////////////////////
8319
8320/**
8321 * Helper method to finalize saving the state.
8322 *
8323 * @note Must be called from under this object's lock.
8324 *
8325 * @param aSuccess TRUE if the snapshot has been taken successfully
8326 *
8327 * @note Locks mParent + this objects for writing.
8328 */
8329HRESULT SessionMachine::endSavingState (BOOL aSuccess)
8330{
8331 LogFlowThisFuncEnter();
8332
8333 AutoCaller autoCaller (this);
8334 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8335
8336 /* mParent->removeProgress() needs mParent lock */
8337 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
8338
8339 HRESULT rc = S_OK;
8340
8341 if (aSuccess)
8342 {
8343 mSSData->mStateFilePath = mSnapshotData.mStateFilePath;
8344
8345 /* save all VM settings */
8346 rc = saveSettings();
8347 }
8348 else
8349 {
8350 /* delete the saved state file (it might have been already created) */
8351 RTFileDelete (Utf8Str (mSnapshotData.mStateFilePath));
8352 }
8353
8354 /* remove the completed progress object */
8355 mParent->removeProgress (mSnapshotData.mProgressId);
8356
8357 /* clear out the temporary saved state data */
8358 mSnapshotData.mLastState = MachineState_InvalidMachineState;
8359 mSnapshotData.mProgressId.clear();
8360 mSnapshotData.mStateFilePath.setNull();
8361
8362 LogFlowThisFuncLeave();
8363 return rc;
8364}
8365
8366/**
8367 * Helper method to finalize taking a snapshot.
8368 * Gets called only from #EndTakingSnapshot() that is expected to
8369 * be called by the VM process when it finishes *all* the tasks related to
8370 * taking a snapshot, either scucessfully or unsuccessfilly.
8371 *
8372 * @param aSuccess TRUE if the snapshot has been taken successfully
8373 *
8374 * @note Locks mParent + this objects for writing.
8375 */
8376HRESULT SessionMachine::endTakingSnapshot (BOOL aSuccess)
8377{
8378 LogFlowThisFuncEnter();
8379
8380 AutoCaller autoCaller (this);
8381 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8382
8383 /* Progress object uninitialization needs mParent lock */
8384 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
8385
8386 HRESULT rc = S_OK;
8387
8388 if (aSuccess)
8389 {
8390 /* the server progress must be completed on success */
8391 Assert (mSnapshotData.mServerProgress->completed());
8392
8393 mData->mCurrentSnapshot = mSnapshotData.mSnapshot;
8394 /* memorize the first snapshot if necessary */
8395 if (!mData->mFirstSnapshot)
8396 mData->mFirstSnapshot = mData->mCurrentSnapshot;
8397
8398 int opFlags = SaveSS_AddOp | SaveSS_UpdateCurrentId;
8399 if (mSnapshotData.mLastState != MachineState_Paused && !isModified())
8400 {
8401 /*
8402 * the machine was powered off or saved when taking a snapshot,
8403 * so reset the mCurrentStateModified flag
8404 */
8405 mData->mCurrentStateModified = FALSE;
8406 opFlags |= SaveSS_UpdateCurStateModified;
8407 }
8408
8409 rc = saveSnapshotSettings (mSnapshotData.mSnapshot, opFlags);
8410 }
8411
8412 if (!aSuccess || FAILED (rc))
8413 {
8414 if (mSnapshotData.mSnapshot)
8415 {
8416 /* wait for the completion of the server progress (diff VDI creation) */
8417 /// @todo (dmik) later, we will definitely want to cancel it instead
8418 // (when the cancel function is implemented)
8419 mSnapshotData.mServerProgress->WaitForCompletion (-1);
8420
8421 /*
8422 * delete all differencing VDIs created
8423 * (this will attach their parents back)
8424 */
8425 rc = deleteSnapshotDiffs (mSnapshotData.mSnapshot);
8426 /* continue cleanup on error */
8427
8428 /* delete the saved state file (it might have been already created) */
8429 if (mSnapshotData.mSnapshot->stateFilePath())
8430 RTFileDelete (Utf8Str (mSnapshotData.mSnapshot->stateFilePath()));
8431
8432 mSnapshotData.mSnapshot->uninit();
8433 }
8434 }
8435
8436 /* inform callbacks */
8437 if (aSuccess && SUCCEEDED (rc))
8438 mParent->onSnapshotTaken (mData->mUuid, mSnapshotData.mSnapshot->data().mId);
8439
8440 /* clear out the snapshot data */
8441 mSnapshotData.mLastState = MachineState_InvalidMachineState;
8442 mSnapshotData.mSnapshot.setNull();
8443 mSnapshotData.mServerProgress.setNull();
8444 /* uninitialize the combined progress (to remove it from the VBox collection) */
8445 if (!mSnapshotData.mCombinedProgress.isNull())
8446 {
8447 mSnapshotData.mCombinedProgress->uninit();
8448 mSnapshotData.mCombinedProgress.setNull();
8449 }
8450
8451 LogFlowThisFuncLeave();
8452 return rc;
8453}
8454
8455/**
8456 * Take snapshot task handler.
8457 * Must be called only by TakeSnapshotTask::handler()!
8458 *
8459 * The sole purpose of this task is to asynchronously create differencing VDIs
8460 * and copy the saved state file (when necessary). The VM process will wait
8461 * for this task to complete using the mSnapshotData.mServerProgress
8462 * returned to it.
8463 *
8464 * @note Locks mParent + this objects for writing.
8465 */
8466void SessionMachine::takeSnapshotHandler (TakeSnapshotTask &aTask)
8467{
8468 LogFlowThisFuncEnter();
8469
8470 AutoCaller autoCaller (this);
8471
8472 LogFlowThisFunc (("state=%d\n", autoCaller.state()));
8473 if (!autoCaller.isOk())
8474 {
8475 /*
8476 * we might have been uninitialized because the session was
8477 * accidentally closed by the client, so don't assert
8478 */
8479 LogFlowThisFuncLeave();
8480 return;
8481 }
8482
8483 /* endTakingSnapshot() needs mParent lock */
8484 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
8485
8486 HRESULT rc = S_OK;
8487
8488 LogFlowThisFunc (("Creating differencing VDIs...\n"));
8489
8490 /* create new differencing hard disks and attach them to this machine */
8491 rc = createSnapshotDiffs (&mSnapshotData.mSnapshot->data().mId,
8492 mUserData->mSnapshotFolderFull,
8493 mSnapshotData.mServerProgress,
8494 true /* aOnline */);
8495
8496 if (SUCCEEDED (rc) && mSnapshotData.mLastState == MachineState_Saved)
8497 {
8498 Utf8Str stateFrom = mSSData->mStateFilePath;
8499 Utf8Str stateTo = mSnapshotData.mSnapshot->stateFilePath();
8500
8501 LogFlowThisFunc (("Copying the execution state from '%s' to '%s'...\n",
8502 stateFrom.raw(), stateTo.raw()));
8503
8504 mSnapshotData.mServerProgress->advanceOperation (
8505 Bstr (tr ("Copying the execution state")));
8506
8507 /*
8508 * We can safely leave the lock here:
8509 * mMachineState is MachineState_Saving here
8510 */
8511 alock.leave();
8512
8513 /* copy the state file */
8514 int vrc = RTFileCopyEx (stateFrom, stateTo, progressCallback,
8515 static_cast <Progress *> (mSnapshotData.mServerProgress));
8516
8517 alock.enter();
8518
8519 if (VBOX_FAILURE (vrc))
8520 rc = setError (E_FAIL,
8521 tr ("Could not copy the state file '%ls' to '%ls' (%Vrc)"),
8522 stateFrom.raw(), stateTo.raw());
8523 }
8524
8525 /*
8526 * we have to call endTakingSnapshot() here if the snapshot was taken
8527 * offline, because the VM process will not do it in this case
8528 */
8529 if (mSnapshotData.mLastState != MachineState_Paused)
8530 {
8531 LogFlowThisFunc (("Finalizing the taken snapshot (rc=%08X)...\n", rc));
8532
8533 setMachineState (mSnapshotData.mLastState);
8534 updateMachineStateOnClient();
8535
8536 /* finalize the progress after setting the state, for consistency */
8537 mSnapshotData.mServerProgress->notifyComplete (rc);
8538
8539 endTakingSnapshot (SUCCEEDED (rc));
8540 }
8541 else
8542 {
8543 mSnapshotData.mServerProgress->notifyComplete (rc);
8544 }
8545
8546 LogFlowThisFuncLeave();
8547}
8548
8549/**
8550 * Discard snapshot task handler.
8551 * Must be called only by DiscardSnapshotTask::handler()!
8552 *
8553 * When aTask.subTask is true, the associated progress object is left
8554 * uncompleted on success. On failure, the progress is marked as completed
8555 * regardless of this parameter.
8556 *
8557 * @note Locks mParent + this + child objects for writing!
8558 */
8559void SessionMachine::discardSnapshotHandler (DiscardSnapshotTask &aTask)
8560{
8561 LogFlowThisFuncEnter();
8562
8563 AutoCaller autoCaller (this);
8564
8565 LogFlowThisFunc (("state=%d\n", autoCaller.state()));
8566 if (!autoCaller.isOk())
8567 {
8568 /*
8569 * we might have been uninitialized because the session was
8570 * accidentally closed by the client, so don't assert
8571 */
8572 aTask.progress->notifyComplete (
8573 E_FAIL, COM_IIDOF (IMachine), getComponentName(),
8574 tr ("The session has been accidentally closed"));
8575
8576 LogFlowThisFuncLeave();
8577 return;
8578 }
8579
8580 ComObjPtr <SnapshotMachine> sm = aTask.snapshot->data().mMachine;
8581
8582 /* mParent is locked because of Progress::notifyComplete(), etc. */
8583 AutoMultiLock <3> alock (mParent->wlock(), this->wlock(), sm->rlock());
8584
8585 /* Safe locking in the direction parent->child */
8586 AutoLock snapshotLock (aTask.snapshot);
8587 AutoLock snapshotChildrenLock (aTask.snapshot->childrenLock());
8588
8589 HRESULT rc = S_OK;
8590
8591 /* save the snapshot ID (for callbacks) */
8592 Guid snapshotId = aTask.snapshot->data().mId;
8593
8594 do
8595 {
8596 /* first pass: */
8597 LogFlowThisFunc (("Check hard disk accessibility and affected machines...\n"));
8598
8599 HDData::HDAttachmentList::const_iterator it;
8600 for (it = sm->mHDData->mHDAttachments.begin();
8601 it != sm->mHDData->mHDAttachments.end();
8602 ++ it)
8603 {
8604 ComObjPtr <HardDiskAttachment> hda = *it;
8605 ComObjPtr <HardDisk> hd = hda->hardDisk();
8606 ComObjPtr <HardDisk> parent = hd->parent();
8607
8608 AutoLock hdLock (hd);
8609
8610 if (hd->hasForeignChildren())
8611 {
8612 rc = setError (E_FAIL,
8613 tr ("One or more hard disks belonging to other machines are "
8614 "based on the hard disk '%ls' stored in the snapshot '%ls'"),
8615 hd->toString().raw(), aTask.snapshot->data().mName.raw());
8616 break;
8617 }
8618
8619 if (hd->type() == HardDiskType_NormalHardDisk)
8620 {
8621 AutoLock hdChildrenLock (hd->childrenLock());
8622 size_t childrenCount = hd->children().size();
8623 if (childrenCount > 1)
8624 {
8625 rc = setError (E_FAIL,
8626 tr ("Normal hard disk '%ls' stored in the snapshot '%ls' "
8627 "has more than one child hard disk (%d)"),
8628 hd->toString().raw(), aTask.snapshot->data().mName.raw(),
8629 childrenCount);
8630 break;
8631 }
8632 }
8633 else
8634 {
8635 ComAssertMsgFailedBreak (("Invalid hard disk type %d\n", hd->type()),
8636 rc = E_FAIL);
8637 }
8638
8639 Bstr accessError;
8640 rc = hd->getAccessibleWithChildren (accessError);
8641 CheckComRCBreakRC (rc);
8642
8643 if (!accessError.isNull())
8644 {
8645 rc = setError (E_FAIL,
8646 tr ("Hard disk '%ls' stored in the snapshot '%ls' is not "
8647 "accessible (%ls)"),
8648 hd->toString().raw(), aTask.snapshot->data().mName.raw(),
8649 accessError.raw());
8650 break;
8651 }
8652
8653 rc = hd->setBusyWithChildren();
8654 if (FAILED (rc))
8655 {
8656 /* reset the busy flag of all previous hard disks */
8657 while (it != sm->mHDData->mHDAttachments.begin())
8658 (*(-- it))->hardDisk()->clearBusyWithChildren();
8659 break;
8660 }
8661 }
8662
8663 CheckComRCBreakRC (rc);
8664
8665 /* second pass: */
8666 LogFlowThisFunc (("Performing actual vdi merging...\n"));
8667
8668 for (it = sm->mHDData->mHDAttachments.begin();
8669 it != sm->mHDData->mHDAttachments.end();
8670 ++ it)
8671 {
8672 ComObjPtr <HardDiskAttachment> hda = *it;
8673 ComObjPtr <HardDisk> hd = hda->hardDisk();
8674 ComObjPtr <HardDisk> parent = hd->parent();
8675
8676 AutoLock hdLock (hd);
8677
8678 Bstr hdRootString = hd->root()->toString (true /* aShort */);
8679
8680 if (parent)
8681 {
8682 if (hd->isParentImmutable())
8683 {
8684 aTask.progress->advanceOperation (Bstr (Utf8StrFmt (
8685 tr ("Discarding changes to immutable hard disk '%ls'"),
8686 hdRootString.raw())));
8687
8688 /* clear the busy flag before unregistering */
8689 hd->clearBusy();
8690
8691 /*
8692 * unregisterDiffHardDisk() is supposed to delete and uninit
8693 * the differencing hard disk
8694 */
8695 rc = mParent->unregisterDiffHardDisk (hd);
8696 CheckComRCBreakRC (rc);
8697 continue;
8698 }
8699 else
8700 {
8701 /*
8702 * differencing VDI:
8703 * merge this image to all its children
8704 */
8705
8706 aTask.progress->advanceOperation (Bstr (Utf8StrFmt (
8707 tr ("Merging changes to normal hard disk '%ls' to children"),
8708 hdRootString.raw())));
8709
8710 snapshotChildrenLock.unlock();
8711 snapshotLock.unlock();
8712 alock.leave();
8713
8714 rc = hd->asVDI()->mergeImageToChildren (aTask.progress);
8715
8716 alock.enter();
8717 snapshotLock.lock();
8718 snapshotChildrenLock.lock();
8719
8720 // debug code
8721 // if (it != sm->mHDData->mHDAttachments.begin())
8722 // {
8723 // rc = setError (E_FAIL, "Simulated failure");
8724 // break;
8725 //}
8726
8727 if (SUCCEEDED (rc))
8728 rc = mParent->unregisterDiffHardDisk (hd);
8729 else
8730 hd->clearBusyWithChildren();
8731
8732 CheckComRCBreakRC (rc);
8733 }
8734 }
8735 else if (hd->type() == HardDiskType_NormalHardDisk)
8736 {
8737 /*
8738 * normal vdi has the only child or none
8739 * (checked in the first pass)
8740 */
8741
8742 ComObjPtr <HardDisk> child;
8743 {
8744 AutoLock hdChildrenLock (hd->childrenLock());
8745 if (hd->children().size())
8746 child = hd->children().front();
8747 }
8748
8749 if (child.isNull())
8750 {
8751 aTask.progress->advanceOperation (Bstr (Utf8StrFmt (
8752 tr ("Detaching normal hard disk '%ls'"),
8753 hdRootString.raw())));
8754
8755 /* just deassociate the normal image from this machine */
8756 hd->setMachineId (Guid());
8757 hd->setSnapshotId (Guid());
8758
8759 /* clear the busy flag */
8760 hd->clearBusy();
8761 }
8762 else
8763 {
8764 AutoLock childLock (child);
8765
8766 aTask.progress->advanceOperation (Bstr (Utf8StrFmt (
8767 tr ("Preserving changes to normal hard disk '%ls'"),
8768 hdRootString.raw())));
8769
8770 ComObjPtr <Machine> cm;
8771 ComObjPtr <Snapshot> cs;
8772 ComObjPtr <HardDiskAttachment> childHda;
8773 rc = findHardDiskAttachment (child, &cm, &cs, &childHda);
8774 CheckComRCBreakRC (rc);
8775 /* must be the same machine (checked in the first pass) */
8776 ComAssertBreak (cm->mData->mUuid == mData->mUuid, rc = E_FAIL);
8777
8778 /* merge the child to this basic image */
8779
8780 snapshotChildrenLock.unlock();
8781 snapshotLock.unlock();
8782 alock.leave();
8783
8784 rc = child->asVDI()->mergeImageToParent (aTask.progress);
8785
8786 alock.enter();
8787 snapshotLock.lock();
8788 snapshotChildrenLock.lock();
8789
8790 if (SUCCEEDED (rc))
8791 rc = mParent->unregisterDiffHardDisk (child);
8792 else
8793 hd->clearBusyWithChildren();
8794
8795 CheckComRCBreakRC (rc);
8796
8797 /* reset the snapshot Id */
8798 hd->setSnapshotId (Guid());
8799
8800 /* replace the child image in the appropriate place */
8801 childHda->updateHardDisk (hd, FALSE /* aDirty */);
8802
8803 if (!cs)
8804 {
8805 aTask.settingsChanged = true;
8806 }
8807 else
8808 {
8809 rc = cm->saveSnapshotSettings (cs, SaveSS_UpdateAllOp);
8810 CheckComRCBreakRC (rc);
8811 }
8812 }
8813 }
8814 else
8815 {
8816 ComAssertMsgFailedBreak (("Invalid hard disk type %d\n", hd->type()),
8817 rc = E_FAIL);
8818 }
8819 }
8820
8821 /* fetch the current error info */
8822 ErrorInfo mergeEi;
8823 HRESULT mergeRc = rc;
8824
8825 if (FAILED (rc))
8826 {
8827 /* clear the busy flag on the rest of hard disks */
8828 for (++ it; it != sm->mHDData->mHDAttachments.end(); ++ it)
8829 (*it)->hardDisk()->clearBusyWithChildren();
8830 }
8831
8832 /*
8833 * we have to try to discard the snapshot even if merging failed
8834 * because some images might have been already merged (and deleted)
8835 */
8836
8837 do
8838 {
8839 LogFlowThisFunc (("Discarding the snapshot (reparenting children)...\n"));
8840
8841 ComObjPtr <Snapshot> parentSnapshot = aTask.snapshot->parent();
8842
8843 /// @todo (dmik):
8844 // when we introduce clones later, discarding the snapshot
8845 // will affect the current and first snapshots of clones, if they are
8846 // direct children of this snapshot. So we will need to lock machines
8847 // associated with child snapshots as well and update mCurrentSnapshot
8848 // and/or mFirstSnapshot fields.
8849
8850 if (aTask.snapshot == mData->mCurrentSnapshot)
8851 {
8852 /* currently, the parent snapshot must refer to the same machine */
8853 ComAssertBreak (
8854 !parentSnapshot ||
8855 parentSnapshot->data().mMachine->mData->mUuid == mData->mUuid,
8856 rc = E_FAIL);
8857 mData->mCurrentSnapshot = parentSnapshot;
8858 /* mark the current state as modified */
8859 mData->mCurrentStateModified = TRUE;
8860 }
8861
8862 if (aTask.snapshot == mData->mFirstSnapshot)
8863 {
8864 /*
8865 * the first snapshot must have only one child when discarded,
8866 * or no children at all
8867 */
8868 ComAssertBreak (aTask.snapshot->children().size() <= 1, rc = E_FAIL);
8869
8870 if (aTask.snapshot->children().size() == 1)
8871 {
8872 ComObjPtr <Snapshot> childSnapshot = aTask.snapshot->children().front();
8873 ComAssertBreak (
8874 childSnapshot->data().mMachine->mData->mUuid == mData->mUuid,
8875 rc = E_FAIL);
8876 mData->mFirstSnapshot = childSnapshot;
8877 }
8878 else
8879 mData->mFirstSnapshot.setNull();
8880 }
8881
8882 /// @todo (dmik)
8883 // if we implement some warning mechanism later, we'll have
8884 // to return a warning if the state file path cannot be deleted
8885 Bstr stateFilePath = aTask.snapshot->stateFilePath();
8886 if (stateFilePath)
8887 RTFileDelete (Utf8Str (stateFilePath));
8888
8889 aTask.snapshot->discard();
8890
8891 rc = saveSnapshotSettings (parentSnapshot,
8892 SaveSS_UpdateAllOp | SaveSS_UpdateCurrentId);
8893 }
8894 while (0);
8895
8896 /* restore the merge error if any */
8897 if (FAILED (mergeRc))
8898 {
8899 rc = mergeRc;
8900 setError (mergeEi);
8901 }
8902 }
8903 while (0);
8904
8905 if (!aTask.subTask || FAILED (rc))
8906 {
8907 if (!aTask.subTask)
8908 {
8909 /* save current error info */
8910 ErrorInfo ei;
8911
8912 /* restore the machine state */
8913 setMachineState (aTask.state);
8914 updateMachineStateOnClient();
8915
8916 /*
8917 * save settings anyway, since we've already changed the current
8918 * machine configuration
8919 */
8920 if (aTask.settingsChanged)
8921 {
8922 saveSettings (true /* aMarkCurStateAsModified */,
8923 true /* aInformCallbacksAnyway */);
8924 }
8925
8926 /* restore current error info */
8927 setError (ei);
8928 }
8929
8930 /* set the result (this will try to fetch current error info on failure) */
8931 aTask.progress->notifyComplete (rc);
8932 }
8933
8934 if (SUCCEEDED (rc))
8935 mParent->onSnapshotDiscarded (mData->mUuid, snapshotId);
8936
8937 LogFlowThisFunc (("Done discarding snapshot (rc=%08X)\n", rc));
8938 LogFlowThisFuncLeave();
8939}
8940
8941/**
8942 * Discard current state task handler.
8943 * Must be called only by DiscardCurrentStateTask::handler()!
8944 *
8945 * @note Locks mParent + this object for writing.
8946 */
8947void SessionMachine::discardCurrentStateHandler (DiscardCurrentStateTask &aTask)
8948{
8949 LogFlowThisFuncEnter();
8950
8951 AutoCaller autoCaller (this);
8952
8953 LogFlowThisFunc (("state=%d\n", autoCaller.state()));
8954 if (!autoCaller.isOk())
8955 {
8956 /*
8957 * we might have been uninitialized because the session was
8958 * accidentally closed by the client, so don't assert
8959 */
8960 aTask.progress->notifyComplete (
8961 E_FAIL, COM_IIDOF (IMachine), getComponentName(),
8962 tr ("The session has been accidentally closed"));
8963
8964 LogFlowThisFuncLeave();
8965 return;
8966 }
8967
8968 /* mParent is locked because of Progress::notifyComplete(), etc. */
8969 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
8970
8971 /*
8972 * discard all current changes to mUserData (name, OSType etc.)
8973 * (note that the machine is powered off, so there is no need
8974 * to inform the direct session)
8975 */
8976 if (isModified())
8977 rollback (false /* aNotify */);
8978
8979 HRESULT rc = S_OK;
8980
8981 bool errorInSubtask = false;
8982 bool stateRestored = false;
8983
8984 const bool isLastSnapshot = mData->mCurrentSnapshot->parent().isNull();
8985
8986 do
8987 {
8988 /*
8989 * discard the saved state file if the machine was Saved prior
8990 * to this operation
8991 */
8992 if (aTask.state == MachineState_Saved)
8993 {
8994 Assert (!mSSData->mStateFilePath.isEmpty());
8995 RTFileDelete (Utf8Str (mSSData->mStateFilePath));
8996 mSSData->mStateFilePath.setNull();
8997 aTask.modifyLastState (MachineState_PoweredOff);
8998 rc = saveStateSettings (SaveSTS_StateFilePath);
8999 CheckComRCBreakRC (rc);
9000 }
9001
9002 if (aTask.discardCurrentSnapshot && !isLastSnapshot)
9003 {
9004 /*
9005 * the "discard current snapshot and state" task is in action,
9006 * the current snapshot is not the last one.
9007 * Discard the current snapshot first.
9008 */
9009
9010 DiscardSnapshotTask subTask (aTask, mData->mCurrentSnapshot);
9011 subTask.subTask = true;
9012 discardSnapshotHandler (subTask);
9013 aTask.settingsChanged = subTask.settingsChanged;
9014 if (aTask.progress->completed())
9015 {
9016 /*
9017 * the progress can be completed by a subtask only if there was
9018 * a failure
9019 */
9020 Assert (FAILED (aTask.progress->resultCode()));
9021 errorInSubtask = true;
9022 rc = aTask.progress->resultCode();
9023 break;
9024 }
9025 }
9026
9027 LONG64 snapshotTimeStamp = 0;
9028
9029 {
9030 ComObjPtr <Snapshot> curSnapshot = mData->mCurrentSnapshot;
9031 AutoLock snapshotLock (curSnapshot);
9032
9033 /* remember the timestamp of the snapshot we're restoring from */
9034 snapshotTimeStamp = curSnapshot->data().mTimeStamp;
9035
9036 /* copy all hardware data from the current snapshot */
9037 copyFrom (curSnapshot->data().mMachine);
9038
9039 LogFlowThisFunc (("Restoring VDIs from the snapshot...\n"));
9040
9041 /* restore the attachmends from the snapshot */
9042 mHDData.backup();
9043 mHDData->mHDAttachments =
9044 curSnapshot->data().mMachine->mHDData->mHDAttachments;
9045
9046 snapshotLock.unlock();
9047 alock.leave();
9048 rc = createSnapshotDiffs (NULL, mUserData->mSnapshotFolderFull,
9049 aTask.progress,
9050 false /* aOnline */);
9051 alock.enter();
9052 snapshotLock.lock();
9053
9054 if (FAILED (rc))
9055 {
9056 /* here we can still safely rollback, so do it */
9057 ErrorInfo ei;
9058 /* undo all changes */
9059 rollback (false /* aNotify */);
9060 setError (ei);
9061 break;
9062 }
9063
9064 /*
9065 * note: old VDIs will be deassociated/deleted on #commit() called
9066 * either from #saveSettings() or directly at the end
9067 */
9068
9069 /* should not have a saved state file associated at this point */
9070 Assert (mSSData->mStateFilePath.isNull());
9071
9072 if (curSnapshot->stateFilePath())
9073 {
9074 Utf8Str snapStateFilePath = curSnapshot->stateFilePath();
9075
9076 Utf8Str stateFilePath = Utf8StrFmt ("%ls%c{%Vuuid}.sav",
9077 mUserData->mSnapshotFolderFull.raw(),
9078 RTPATH_DELIMITER, mData->mUuid.raw());
9079
9080 LogFlowThisFunc (("Copying saved state file from '%s' to '%s'...\n",
9081 snapStateFilePath.raw(), stateFilePath.raw()));
9082
9083 aTask.progress->advanceOperation (
9084 Bstr (tr ("Restoring the execution state")));
9085
9086 /* copy the state file */
9087 snapshotLock.unlock();
9088 alock.leave();
9089 int vrc = RTFileCopyEx (snapStateFilePath, stateFilePath,
9090 progressCallback, aTask.progress);
9091 alock.enter();
9092 snapshotLock.lock();
9093
9094 if (VBOX_SUCCESS (vrc))
9095 {
9096 mSSData->mStateFilePath = stateFilePath;
9097 }
9098 else
9099 {
9100 rc = setError (E_FAIL,
9101 tr ("Could not copy the state file '%s' to '%s' (%Vrc)"),
9102 snapStateFilePath.raw(), stateFilePath.raw(), vrc);
9103 break;
9104 }
9105 }
9106 }
9107
9108 bool informCallbacks = false;
9109
9110 if (aTask.discardCurrentSnapshot && isLastSnapshot)
9111 {
9112 /*
9113 * discard the current snapshot and state task is in action,
9114 * the current snapshot is the last one.
9115 * Discard the current snapshot after discarding the current state.
9116 */
9117
9118 /* commit changes to fixup hard disks before discarding */
9119 rc = commit();
9120 if (SUCCEEDED (rc))
9121 {
9122 DiscardSnapshotTask subTask (aTask, mData->mCurrentSnapshot);
9123 subTask.subTask = true;
9124 discardSnapshotHandler (subTask);
9125 aTask.settingsChanged = subTask.settingsChanged;
9126 if (aTask.progress->completed())
9127 {
9128 /*
9129 * the progress can be completed by a subtask only if there
9130 * was a failure
9131 */
9132 Assert (FAILED (aTask.progress->resultCode()));
9133 errorInSubtask = true;
9134 rc = aTask.progress->resultCode();
9135 }
9136 }
9137
9138 /*
9139 * we've committed already, so inform callbacks anyway to ensure
9140 * they don't miss some change
9141 */
9142 informCallbacks = true;
9143 }
9144
9145 /*
9146 * we have already discarded the current state, so set the
9147 * execution state accordingly no matter of the discard snapshot result
9148 */
9149 if (mSSData->mStateFilePath)
9150 setMachineState (MachineState_Saved);
9151 else
9152 setMachineState (MachineState_PoweredOff);
9153
9154 updateMachineStateOnClient();
9155 stateRestored = true;
9156
9157 if (errorInSubtask)
9158 break;
9159
9160 /* assign the timestamp from the snapshot */
9161 Assert (snapshotTimeStamp != 0);
9162 mData->mLastStateChange = snapshotTimeStamp;
9163
9164 /* mark the current state as not modified */
9165 mData->mCurrentStateModified = FALSE;
9166
9167 /* save all settings and commit */
9168 rc = saveSettings (false /* aMarkCurStateAsModified */,
9169 informCallbacks);
9170 aTask.settingsChanged = false;
9171 }
9172 while (0);
9173
9174 if (FAILED (rc))
9175 {
9176 ErrorInfo ei;
9177
9178 if (!stateRestored)
9179 {
9180 /* restore the machine state */
9181 setMachineState (aTask.state);
9182 updateMachineStateOnClient();
9183 }
9184
9185 /*
9186 * save all settings and commit if still modified (there is no way to
9187 * rollback properly). Note that isModified() will return true after
9188 * copyFrom(). Also save the settings if requested by the subtask.
9189 */
9190 if (isModified() || aTask.settingsChanged)
9191 {
9192 if (aTask.settingsChanged)
9193 saveSettings (true /* aMarkCurStateAsModified */,
9194 true /* aInformCallbacksAnyway */);
9195 else
9196 saveSettings();
9197 }
9198
9199 setError (ei);
9200 }
9201
9202 if (!errorInSubtask)
9203 {
9204 /* set the result (this will try to fetch current error info on failure) */
9205 aTask.progress->notifyComplete (rc);
9206 }
9207
9208 if (SUCCEEDED (rc))
9209 mParent->onSnapshotDiscarded (mData->mUuid, Guid());
9210
9211 LogFlowThisFunc (("Done discarding current state (rc=%08X)\n", rc));
9212
9213 LogFlowThisFuncLeave();
9214}
9215
9216/**
9217 * Helper to change the machine state (reimplementation).
9218 *
9219 * @note Locks this object for writing.
9220 */
9221HRESULT SessionMachine::setMachineState (MachineState_T aMachineState)
9222{
9223 LogFlowThisFuncEnter();
9224 LogFlowThisFunc (("aMachineState=%d\n", aMachineState));
9225
9226 AutoCaller autoCaller (this);
9227 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
9228
9229 AutoLock alock (this);
9230
9231 MachineState_T oldMachineState = mData->mMachineState;
9232
9233 AssertMsgReturn (oldMachineState != aMachineState,
9234 ("oldMachineState=%d, aMachineState=%d\n",
9235 oldMachineState, aMachineState), E_FAIL);
9236
9237 HRESULT rc = S_OK;
9238
9239 int stsFlags = 0;
9240 bool deleteSavedState = false;
9241
9242 /* detect some state transitions */
9243
9244 if (oldMachineState < MachineState_Running &&
9245 aMachineState >= MachineState_Running &&
9246 aMachineState != MachineState_Discarding)
9247 {
9248 /*
9249 * the EMT thread is about to start, so mark attached HDDs as busy
9250 * and all its ancestors as being in use
9251 */
9252 for (HDData::HDAttachmentList::const_iterator it =
9253 mHDData->mHDAttachments.begin();
9254 it != mHDData->mHDAttachments.end();
9255 ++ it)
9256 {
9257 ComObjPtr <HardDisk> hd = (*it)->hardDisk();
9258 AutoLock hdLock (hd);
9259 hd->setBusy();
9260 hd->addReaderOnAncestors();
9261 }
9262 }
9263 else
9264 if (oldMachineState >= MachineState_Running &&
9265 oldMachineState != MachineState_Discarding &&
9266 aMachineState < MachineState_Running)
9267 {
9268 /*
9269 * the EMT thread stopped, so mark attached HDDs as no more busy
9270 * and remove the in-use flag from all its ancestors
9271 */
9272 for (HDData::HDAttachmentList::const_iterator it =
9273 mHDData->mHDAttachments.begin();
9274 it != mHDData->mHDAttachments.end();
9275 ++ it)
9276 {
9277 ComObjPtr <HardDisk> hd = (*it)->hardDisk();
9278 AutoLock hdLock (hd);
9279 hd->releaseReaderOnAncestors();
9280 hd->clearBusy();
9281 }
9282 }
9283
9284 if (oldMachineState == MachineState_Restoring)
9285 {
9286 if (aMachineState != MachineState_Saved)
9287 {
9288 /*
9289 * delete the saved state file once the machine has finished
9290 * restoring from it (note that Console sets the state from
9291 * Restoring to Saved if the VM couldn't restore successfully,
9292 * to give the user an ability to fix an error and retry --
9293 * we keep the saved state file in this case)
9294 */
9295 deleteSavedState = true;
9296 }
9297 }
9298 else
9299 if (oldMachineState == MachineState_Saved &&
9300 (aMachineState == MachineState_PoweredOff ||
9301 aMachineState == MachineState_Aborted))
9302 {
9303 /*
9304 * delete the saved state after Console::DiscardSavedState() is called
9305 * or if the VM process (owning a direct VM session) crashed while the
9306 * VM was Saved
9307 */
9308
9309 /// @todo (dmik)
9310 // Not sure that deleting the saved state file just because of the
9311 // client death before it attempted to restore the VM is a good
9312 // thing. But when it crashes we need to go to the Aborted state
9313 // which cannot have the saved state file associated... The only
9314 // way to fix this is to make the Aborted condition not a VM state
9315 // but a bool flag: i.e., when a crash occurs, set it to true and
9316 // change the state to PoweredOff or Saved depending on the
9317 // saved state presence.
9318
9319 deleteSavedState = true;
9320 mData->mCurrentStateModified = TRUE;
9321 stsFlags |= SaveSTS_CurStateModified;
9322 }
9323
9324 if (aMachineState == MachineState_Starting ||
9325 aMachineState == MachineState_Restoring)
9326 {
9327 /*
9328 * set the current state modified flag to indicate that the
9329 * current state is no more identical to the state in the
9330 * current snapshot
9331 */
9332 if (!mData->mCurrentSnapshot.isNull())
9333 {
9334 mData->mCurrentStateModified = TRUE;
9335 stsFlags |= SaveSTS_CurStateModified;
9336 }
9337 }
9338
9339 if (deleteSavedState == true)
9340 {
9341 Assert (!mSSData->mStateFilePath.isEmpty());
9342 RTFileDelete (Utf8Str (mSSData->mStateFilePath));
9343 mSSData->mStateFilePath.setNull();
9344 stsFlags |= SaveSTS_StateFilePath;
9345 }
9346
9347 /* redirect to the underlying peer machine */
9348 mPeer->setMachineState (aMachineState);
9349
9350 if (aMachineState == MachineState_PoweredOff ||
9351 aMachineState == MachineState_Aborted ||
9352 aMachineState == MachineState_Saved)
9353 {
9354 stsFlags |= SaveSTS_StateTimeStamp;
9355 }
9356
9357 rc = saveStateSettings (stsFlags);
9358
9359 if ((oldMachineState != MachineState_PoweredOff &&
9360 oldMachineState != MachineState_Aborted) &&
9361 (aMachineState == MachineState_PoweredOff ||
9362 aMachineState == MachineState_Aborted))
9363 {
9364 /*
9365 * clear differencing hard disks based on immutable hard disks
9366 * once we've been shut down for any reason
9367 */
9368 rc = wipeOutImmutableDiffs();
9369 }
9370
9371 LogFlowThisFunc (("rc=%08X\n", rc));
9372 LogFlowThisFuncLeave();
9373 return rc;
9374}
9375
9376/**
9377 * Sends the current machine state value to the VM process.
9378 *
9379 * @note Locks this object for reading, then calls a client process.
9380 */
9381HRESULT SessionMachine::updateMachineStateOnClient()
9382{
9383 AutoCaller autoCaller (this);
9384 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
9385
9386 ComPtr <IInternalSessionControl> directControl;
9387 {
9388 AutoReaderLock alock (this);
9389 AssertReturn (!!mData, E_FAIL);
9390 directControl = mData->mSession.mDirectControl;
9391
9392 /* directControl may be already set to NULL here in #OnSessionEnd()
9393 * called too early by the direct session process while there is still
9394 * some operation (like discarding the snapshot) in progress. The client
9395 * process in this case is waiting inside Session::close() for the
9396 * "end session" process object to complete, while #uninit() called by
9397 * #checkForDeath() on the Watcher thread is waiting for the pending
9398 * operation to complete. For now, we accept this inconsitent behavior
9399 * and simply do nothing here. */
9400
9401 if (mData->mSession.mState == SessionState_SessionClosing)
9402 return S_OK;
9403
9404 AssertReturn (!directControl.isNull(), E_FAIL);
9405 }
9406
9407 return directControl->UpdateMachineState (mData->mMachineState);
9408}
9409
9410/* static */
9411DECLCALLBACK(int) SessionMachine::taskHandler (RTTHREAD thread, void *pvUser)
9412{
9413 AssertReturn (pvUser, VERR_INVALID_POINTER);
9414
9415 Task *task = static_cast <Task *> (pvUser);
9416 task->handler();
9417
9418 // it's our responsibility to delete the task
9419 delete task;
9420
9421 return 0;
9422}
9423
9424/////////////////////////////////////////////////////////////////////////////
9425// SnapshotMachine class
9426/////////////////////////////////////////////////////////////////////////////
9427
9428DEFINE_EMPTY_CTOR_DTOR (SnapshotMachine)
9429
9430HRESULT SnapshotMachine::FinalConstruct()
9431{
9432 LogFlowThisFunc (("\n"));
9433
9434 /* set the proper type to indicate we're the SnapshotMachine instance */
9435 unconst (mType) = IsSnapshotMachine;
9436
9437 return S_OK;
9438}
9439
9440void SnapshotMachine::FinalRelease()
9441{
9442 LogFlowThisFunc (("\n"));
9443
9444 uninit();
9445}
9446
9447/**
9448 * Initializes the SnapshotMachine object when taking a snapshot.
9449 *
9450 * @param aSessionMachine machine to take a snapshot from
9451 * @param aSnapshotId snapshot ID of this snapshot machine
9452 * @param aStateFilePath file where the execution state will be later saved
9453 * (or NULL for the offline snapshot)
9454 *
9455 * @note Locks aSessionMachine object for reading.
9456 */
9457HRESULT SnapshotMachine::init (SessionMachine *aSessionMachine,
9458 INPTR GUIDPARAM aSnapshotId,
9459 INPTR BSTR aStateFilePath)
9460{
9461 LogFlowThisFuncEnter();
9462 LogFlowThisFunc (("mName={%ls}\n", aSessionMachine->mUserData->mName.raw()));
9463
9464 AssertReturn (aSessionMachine && !Guid (aSnapshotId).isEmpty(), E_INVALIDARG);
9465
9466 /* Enclose the state transition NotReady->InInit->Ready */
9467 AutoInitSpan autoInitSpan (this);
9468 AssertReturn (autoInitSpan.isOk(), E_UNEXPECTED);
9469
9470 mSnapshotId = aSnapshotId;
9471
9472 AutoReaderLock alock (aSessionMachine);
9473
9474 /* memorize the primary Machine instance (i.e. not SessionMachine!) */
9475 unconst (mPeer) = aSessionMachine->mPeer;
9476 /* share the parent pointer */
9477 unconst (mParent) = mPeer->mParent;
9478
9479 /* take the pointer to Data to share */
9480 mData.share (mPeer->mData);
9481 /*
9482 * take the pointer to UserData to share
9483 * (our UserData must always be the same as Machine's data)
9484 */
9485 mUserData.share (mPeer->mUserData);
9486 /* make a private copy of all other data (recent changes from SessionMachine) */
9487 mHWData.attachCopy (aSessionMachine->mHWData);
9488 mHDData.attachCopy (aSessionMachine->mHDData);
9489
9490 /* SSData is always unique for SnapshotMachine */
9491 mSSData.allocate();
9492 mSSData->mStateFilePath = aStateFilePath;
9493
9494 /*
9495 * create copies of all shared folders (mHWData after attiching a copy
9496 * contains just references to original objects)
9497 */
9498 for (HWData::SharedFolderList::iterator it = mHWData->mSharedFolders.begin();
9499 it != mHWData->mSharedFolders.end();
9500 ++ it)
9501 {
9502 ComObjPtr <SharedFolder> folder;
9503 folder.createObject();
9504 HRESULT rc = folder->initCopy (this, *it);
9505 CheckComRCReturnRC (rc);
9506 *it = folder;
9507 }
9508
9509 /* create all other child objects that will be immutable private copies */
9510
9511 unconst (mBIOSSettings).createObject();
9512 mBIOSSettings->initCopy (this, mPeer->mBIOSSettings);
9513
9514#ifdef VBOX_VRDP
9515 unconst (mVRDPServer).createObject();
9516 mVRDPServer->initCopy (this, mPeer->mVRDPServer);
9517#endif
9518
9519 unconst (mDVDDrive).createObject();
9520 mDVDDrive->initCopy (this, mPeer->mDVDDrive);
9521
9522 unconst (mFloppyDrive).createObject();
9523 mFloppyDrive->initCopy (this, mPeer->mFloppyDrive);
9524
9525 unconst (mAudioAdapter).createObject();
9526 mAudioAdapter->initCopy (this, mPeer->mAudioAdapter);
9527
9528 unconst (mUSBController).createObject();
9529 mUSBController->initCopy (this, mPeer->mUSBController);
9530
9531 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
9532 {
9533 unconst (mNetworkAdapters [slot]).createObject();
9534 mNetworkAdapters [slot]->initCopy (this, mPeer->mNetworkAdapters [slot]);
9535 }
9536
9537 /* Confirm a successful initialization when it's the case */
9538 autoInitSpan.setSucceeded();
9539
9540 LogFlowThisFuncLeave();
9541 return S_OK;
9542}
9543
9544/**
9545 * Initializes the SnapshotMachine object when loading from the settings file.
9546 *
9547 * @param aMachine machine the snapshot belngs to
9548 * @param aHWNode <Hardware> node
9549 * @param aHDAsNode <HardDiskAttachments> node
9550 * @param aSnapshotId snapshot ID of this snapshot machine
9551 * @param aStateFilePath file where the execution state is saved
9552 * (or NULL for the offline snapshot)
9553 *
9554 * @note Locks aMachine object for reading.
9555 */
9556HRESULT SnapshotMachine::init (Machine *aMachine, CFGNODE aHWNode, CFGNODE aHDAsNode,
9557 INPTR GUIDPARAM aSnapshotId, INPTR BSTR aStateFilePath)
9558{
9559 LogFlowThisFuncEnter();
9560 LogFlowThisFunc (("mName={%ls}\n", aMachine->mUserData->mName.raw()));
9561
9562 AssertReturn (aMachine && aHWNode && aHDAsNode && !Guid (aSnapshotId).isEmpty(),
9563 E_INVALIDARG);
9564
9565 /* Enclose the state transition NotReady->InInit->Ready */
9566 AutoInitSpan autoInitSpan (this);
9567 AssertReturn (autoInitSpan.isOk(), E_UNEXPECTED);
9568
9569 mSnapshotId = aSnapshotId;
9570
9571 AutoReaderLock alock (aMachine);
9572
9573 /* memorize the primary Machine instance */
9574 unconst (mPeer) = aMachine;
9575 /* share the parent pointer */
9576 unconst (mParent) = mPeer->mParent;
9577
9578 /* take the pointer to Data to share */
9579 mData.share (mPeer->mData);
9580 /*
9581 * take the pointer to UserData to share
9582 * (our UserData must always be the same as Machine's data)
9583 */
9584 mUserData.share (mPeer->mUserData);
9585 /* allocate private copies of all other data (will be loaded from settings) */
9586 mHWData.allocate();
9587 mHDData.allocate();
9588
9589 /* SSData is always unique for SnapshotMachine */
9590 mSSData.allocate();
9591 mSSData->mStateFilePath = aStateFilePath;
9592
9593 /* create all other child objects that will be immutable private copies */
9594
9595 unconst (mBIOSSettings).createObject();
9596 mBIOSSettings->init (this);
9597
9598#ifdef VBOX_VRDP
9599 unconst (mVRDPServer).createObject();
9600 mVRDPServer->init (this);
9601#endif
9602
9603 unconst (mDVDDrive).createObject();
9604 mDVDDrive->init (this);
9605
9606 unconst (mFloppyDrive).createObject();
9607 mFloppyDrive->init (this);
9608
9609 unconst (mAudioAdapter).createObject();
9610 mAudioAdapter->init (this);
9611
9612 unconst (mUSBController).createObject();
9613 mUSBController->init (this);
9614
9615 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
9616 {
9617 unconst (mNetworkAdapters [slot]).createObject();
9618 mNetworkAdapters [slot]->init (this, slot);
9619 }
9620
9621 /* load hardware and harddisk settings */
9622
9623 HRESULT rc = loadHardware (aHWNode);
9624 if (SUCCEEDED (rc))
9625 rc = loadHardDisks (aHDAsNode, true /* aRegistered */, &mSnapshotId);
9626
9627 if (SUCCEEDED (rc))
9628 {
9629 /* commit all changes made during the initialization */
9630 commit();
9631 }
9632
9633 /* Confirm a successful initialization when it's the case */
9634 if (SUCCEEDED (rc))
9635 autoInitSpan.setSucceeded();
9636
9637 LogFlowThisFuncLeave();
9638 return rc;
9639}
9640
9641/**
9642 * Uninitializes this SnapshotMachine object.
9643 */
9644void SnapshotMachine::uninit()
9645{
9646 LogFlowThisFuncEnter();
9647
9648 /* Enclose the state transition Ready->InUninit->NotReady */
9649 AutoUninitSpan autoUninitSpan (this);
9650 if (autoUninitSpan.uninitDone())
9651 return;
9652
9653 uninitDataAndChildObjects();
9654
9655 unconst (mParent).setNull();
9656 unconst (mPeer).setNull();
9657
9658 LogFlowThisFuncLeave();
9659}
9660
9661// AutoLock::Lockable interface
9662////////////////////////////////////////////////////////////////////////////////
9663
9664/**
9665 * Overrides VirtualBoxBase::lockHandle() in order to share the lock handle
9666 * with the primary Machine instance (mPeer).
9667 */
9668AutoLock::Handle *SnapshotMachine::lockHandle() const
9669{
9670 AssertReturn (!mPeer.isNull(), NULL);
9671 return mPeer->lockHandle();
9672}
9673
9674// public methods only for internal purposes
9675////////////////////////////////////////////////////////////////////////////////
9676
9677/**
9678 * Called by the snapshot object associated with this SnapshotMachine when
9679 * snapshot data such as name or description is changed.
9680 *
9681 * @note Locks this object for writing.
9682 */
9683HRESULT SnapshotMachine::onSnapshotChange (Snapshot *aSnapshot)
9684{
9685 AutoLock alock (this);
9686
9687 mPeer->saveSnapshotSettings (aSnapshot, SaveSS_UpdateAttrsOp);
9688
9689 /* inform callbacks */
9690 mParent->onSnapshotChange (mData->mUuid, aSnapshot->data().mId);
9691
9692 return S_OK;
9693}
Note: See TracBrowser for help on using the repository browser.

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