VirtualBox

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

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

Main: do not include include/VBox/settings.h from other header files but only from implementations that need it (save compile time)

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