VirtualBox

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

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

PerfAPI: Windows collector re-worked to extract raw counters via WMI. Filtering for queries added. Security initialization is added to svcmain to access perf enumerators.

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