VirtualBox

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

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

Main: Fixed r41667.

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