VirtualBox

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

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

Main: Exmplicit machine state comparisons replaced with bool "IsMetastate"-like methods.

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

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