VirtualBox

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

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

Enable the OHCI and EHCI controller by default for new VMs.

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

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