VirtualBox

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

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

fix compiler warning

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