VirtualBox

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

Last change on this file since 1 was 1, checked in by vboxsync, 55 years ago

import

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

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