VirtualBox

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

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

accident

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