VirtualBox

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

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

Async USB detach (for darwin) with async operation timeout (not enabled on windows). Drop the USBProxyService::reset method. (Hope I didn't break anything, it's only tested on Darwin...)

  • 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.215389.xyz. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License as published by the Free Software Foundation,
13 * in version 2 as it comes in the "COPYING" file of the VirtualBox OSE
14 * distribution. VirtualBox OSE is distributed in the hope that it will
15 * be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * If you received this file as part of a commercial VirtualBox
18 * distribution, then only the terms of your commercial VirtualBox
19 * license agreement apply instead of the previous paragraph.
20 */
21
22#if defined(__WIN__)
23#elif defined(__LINUX__)
24#endif
25
26#ifdef VBOX_WITH_SYS_V_IPC_SESSION_WATCHER
27# include <errno.h>
28# include <sys/types.h>
29# include <sys/stat.h>
30# include <sys/ipc.h>
31# include <sys/sem.h>
32#endif
33
34#include "VirtualBoxImpl.h"
35#include "MachineImpl.h"
36#include "HardDiskImpl.h"
37#include "HostDVDDriveImpl.h"
38#include "HostFloppyDriveImpl.h"
39#include "ProgressImpl.h"
40#include "HardDiskAttachmentImpl.h"
41#include "USBControllerImpl.h"
42#include "HostImpl.h"
43#include "SystemPropertiesImpl.h"
44#include "SharedFolderImpl.h"
45#include "GuestOSTypeImpl.h"
46#include "VirtualBoxErrorInfoImpl.h"
47
48#include "USBProxyService.h"
49
50#include "Logging.h"
51
52#include <stdio.h>
53#include <stdlib.h>
54#include <VBox/err.h>
55#include <VBox/cfgldr.h>
56#include <iprt/path.h>
57#include <iprt/dir.h>
58#include <iprt/asm.h>
59#include <iprt/process.h>
60#include <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::detachAllUSBDevices() 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 DetachAllUSBDevices (TRUE /* aDone */);
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::detachUSBDevice() does.
7891 */
7892STDMETHODIMP SessionMachine::DetachUSBDevice (INPTR GUIDPARAM aId, BOOL aDone)
7893{
7894 LogFlowThisFunc (("\n"));
7895
7896 AutoCaller autoCaller (this);
7897 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7898
7899 return mParent->host()->detachUSBDevice (this, aId, aDone);
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::detachAllUSBDevices().
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::DetachAllUSBDevices(BOOL aDone)
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()->detachAllUSBDevices (this, aDone);
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.

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