VirtualBox

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

Last change on this file since 3516 was 3494, checked in by vboxsync, 17 years ago

added support for serial ports to Main and VBoxManage

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

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