VirtualBox

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

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

FE/Qt: Disabled VRDP UI when IMachine::VRDPServer is not available (i.e. in OSE).

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