VirtualBox

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

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

Main: Fixed the potential deadlock in Machine::openSession() when opening a session fails for some reason (#3653).

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