VirtualBox

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

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

Main: add read/write param to OpenHardDisk to allow for opening disk images during import without requiring write access

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

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