VirtualBox

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

Last change on this file since 19610 was 19610, checked in by vboxsync, 16 years ago

Main/Machine: add extradata key to suppress deleting saved state on discarding it.

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

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