VirtualBox

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

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

Main/GuestProperties: the session machine directControl pointer can be NULL, check this

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