VirtualBox

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

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

Main: Rework storage controller handling to allow an arbitrary number of different storage controllers and remove code duplication:

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

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