VirtualBox

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

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

Main/Unix: redo SYSV semaphore key generation. The old solution was prone to collisions, which led to mysterious hangs at VM poweroff for the second VM which accidentally shared the same semaphore. Additionally tightened access rights.

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

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