VirtualBox

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

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

Mac OS X audio support (only playback).

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

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