VirtualBox

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

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

Main: Use named mutexes for watching client processes on OS/2.

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