VirtualBox

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

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

Guest properties: initial commit of new interface

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 329.2 KB
Line 
1/* $Id: MachineImpl.cpp 10797 2008-07-22 08:12:42Z 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)
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 /* Create and register base metrics */
7370 IUnknown *objptr;
7371
7372 ComObjPtr<Machine> tmp = this;
7373 tmp.queryInterfaceTo (&objptr);
7374 pm::BaseMetric *cpuLoad =
7375 metricFactory->createMachineCpuLoad (objptr, mData->mSession.mPid,
7376 cpuLoadUser, cpuLoadKernel);
7377 aCollector->registerBaseMetric (cpuLoad);
7378
7379 aCollector->registerMetric (new pm::Metric(cpuLoad, cpuLoadUser, 0));
7380 aCollector->registerMetric (new pm::Metric(cpuLoad, cpuLoadUser,
7381 new pm::AggregateAvg()));
7382 aCollector->registerMetric (new pm::Metric(cpuLoad, cpuLoadUser,
7383 new pm::AggregateMin()));
7384 aCollector->registerMetric (new pm::Metric(cpuLoad, cpuLoadUser,
7385 new pm::AggregateMax()));
7386 aCollector->registerMetric (new pm::Metric(cpuLoad, cpuLoadKernel, 0));
7387 aCollector->registerMetric (new pm::Metric(cpuLoad, cpuLoadKernel,
7388 new pm::AggregateAvg()));
7389 aCollector->registerMetric (new pm::Metric(cpuLoad, cpuLoadKernel,
7390 new pm::AggregateMin()));
7391 aCollector->registerMetric (new pm::Metric(cpuLoad, cpuLoadKernel,
7392 new pm::AggregateMax()));
7393};
7394
7395void Machine::unregisterMetrics (PerformanceCollector *aCollector)
7396{
7397 aCollector->unregisterMetricsFor (this);
7398 aCollector->unregisterBaseMetricsFor (this);
7399};
7400#endif /* VBOX_WITH_RESOURCE_USAGE_API */
7401
7402
7403/////////////////////////////////////////////////////////////////////////////
7404// SessionMachine class
7405/////////////////////////////////////////////////////////////////////////////
7406
7407/** Task structure for asynchronous VM operations */
7408struct SessionMachine::Task
7409{
7410 Task (SessionMachine *m, Progress *p)
7411 : machine (m), progress (p)
7412 , state (m->mData->mMachineState) // save the current machine state
7413 , subTask (false), settingsChanged (false)
7414 {}
7415
7416 void modifyLastState (MachineState_T s)
7417 {
7418 *const_cast <MachineState_T *> (&state) = s;
7419 }
7420
7421 virtual void handler() = 0;
7422
7423 const ComObjPtr <SessionMachine> machine;
7424 const ComObjPtr <Progress> progress;
7425 const MachineState_T state;
7426
7427 bool subTask : 1;
7428 bool settingsChanged : 1;
7429};
7430
7431/** Take snapshot task */
7432struct SessionMachine::TakeSnapshotTask : public SessionMachine::Task
7433{
7434 TakeSnapshotTask (SessionMachine *m)
7435 : Task (m, NULL) {}
7436
7437 void handler() { machine->takeSnapshotHandler (*this); }
7438};
7439
7440/** Discard snapshot task */
7441struct SessionMachine::DiscardSnapshotTask : public SessionMachine::Task
7442{
7443 DiscardSnapshotTask (SessionMachine *m, Progress *p, Snapshot *s)
7444 : Task (m, p)
7445 , snapshot (s) {}
7446
7447 DiscardSnapshotTask (const Task &task, Snapshot *s)
7448 : Task (task)
7449 , snapshot (s) {}
7450
7451 void handler() { machine->discardSnapshotHandler (*this); }
7452
7453 const ComObjPtr <Snapshot> snapshot;
7454};
7455
7456/** Discard current state task */
7457struct SessionMachine::DiscardCurrentStateTask : public SessionMachine::Task
7458{
7459 DiscardCurrentStateTask (SessionMachine *m, Progress *p,
7460 bool discardCurSnapshot)
7461 : Task (m, p), discardCurrentSnapshot (discardCurSnapshot) {}
7462
7463 void handler() { machine->discardCurrentStateHandler (*this); }
7464
7465 const bool discardCurrentSnapshot;
7466};
7467
7468////////////////////////////////////////////////////////////////////////////////
7469
7470DEFINE_EMPTY_CTOR_DTOR (SessionMachine)
7471
7472HRESULT SessionMachine::FinalConstruct()
7473{
7474 LogFlowThisFunc (("\n"));
7475
7476 /* set the proper type to indicate we're the SessionMachine instance */
7477 unconst (mType) = IsSessionMachine;
7478
7479#if defined(RT_OS_WINDOWS)
7480 mIPCSem = NULL;
7481#elif defined(RT_OS_OS2)
7482 mIPCSem = NULLHANDLE;
7483#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
7484 mIPCSem = -1;
7485#else
7486# error "Port me!"
7487#endif
7488
7489 return S_OK;
7490}
7491
7492void SessionMachine::FinalRelease()
7493{
7494 LogFlowThisFunc (("\n"));
7495
7496 uninit (Uninit::Unexpected);
7497}
7498
7499/**
7500 * @note Must be called only by Machine::openSession() from its own write lock.
7501 */
7502HRESULT SessionMachine::init (Machine *aMachine)
7503{
7504 LogFlowThisFuncEnter();
7505 LogFlowThisFunc (("mName={%ls}\n", aMachine->mUserData->mName.raw()));
7506
7507 AssertReturn (aMachine, E_INVALIDARG);
7508
7509 AssertReturn (aMachine->lockHandle()->isWriteLockOnCurrentThread(), E_FAIL);
7510
7511 /* Enclose the state transition NotReady->InInit->Ready */
7512 AutoInitSpan autoInitSpan (this);
7513 AssertReturn (autoInitSpan.isOk(), E_UNEXPECTED);
7514
7515 /* create the interprocess semaphore */
7516#if defined(RT_OS_WINDOWS)
7517 mIPCSemName = aMachine->mData->mConfigFileFull;
7518 for (size_t i = 0; i < mIPCSemName.length(); i++)
7519 if (mIPCSemName[i] == '\\')
7520 mIPCSemName[i] = '/';
7521 mIPCSem = ::CreateMutex (NULL, FALSE, mIPCSemName);
7522 ComAssertMsgRet (mIPCSem,
7523 ("Cannot create IPC mutex '%ls', err=%d\n",
7524 mIPCSemName.raw(), ::GetLastError()),
7525 E_FAIL);
7526#elif defined(RT_OS_OS2)
7527 Utf8Str ipcSem = Utf8StrFmt ("\\SEM32\\VBOX\\VM\\{%Vuuid}",
7528 aMachine->mData->mUuid.raw());
7529 mIPCSemName = ipcSem;
7530 APIRET arc = ::DosCreateMutexSem ((PSZ) ipcSem.raw(), &mIPCSem, 0, FALSE);
7531 ComAssertMsgRet (arc == NO_ERROR,
7532 ("Cannot create IPC mutex '%s', arc=%ld\n",
7533 ipcSem.raw(), arc),
7534 E_FAIL);
7535#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
7536 Utf8Str configFile = aMachine->mData->mConfigFileFull;
7537 char *configFileCP = NULL;
7538 int error;
7539 RTStrUtf8ToCurrentCP (&configFileCP, configFile);
7540 key_t key = ::ftok (configFileCP, 0);
7541 RTStrFree (configFileCP);
7542 mIPCSem = ::semget (key, 1, S_IRWXU | S_IRWXG | S_IRWXO | IPC_CREAT);
7543 error = errno;
7544 if (mIPCSem < 0 && error == ENOSYS)
7545 {
7546 setError(E_FAIL,
7547 tr ("Cannot create IPC semaphore. Most likely your host kernel lacks "
7548 "support for SysV IPC. Check the host kernel configuration for "
7549 "CONFIG_SYSVIPC=y"));
7550 return E_FAIL;
7551 }
7552 ComAssertMsgRet (mIPCSem >= 0, ("Cannot create IPC semaphore, errno=%d", error),
7553 E_FAIL);
7554 /* set the initial value to 1 */
7555 int rv = ::semctl (mIPCSem, 0, SETVAL, 1);
7556 ComAssertMsgRet (rv == 0, ("Cannot init IPC semaphore, errno=%d", errno),
7557 E_FAIL);
7558#else
7559# error "Port me!"
7560#endif
7561
7562 /* memorize the peer Machine */
7563 unconst (mPeer) = aMachine;
7564 /* share the parent pointer */
7565 unconst (mParent) = aMachine->mParent;
7566
7567 /* take the pointers to data to share */
7568 mData.share (aMachine->mData);
7569 mSSData.share (aMachine->mSSData);
7570
7571 mUserData.share (aMachine->mUserData);
7572 mHWData.share (aMachine->mHWData);
7573 mHDData.share (aMachine->mHDData);
7574
7575 unconst (mBIOSSettings).createObject();
7576 mBIOSSettings->init (this, aMachine->mBIOSSettings);
7577#ifdef VBOX_VRDP
7578 /* create another VRDPServer object that will be mutable */
7579 unconst (mVRDPServer).createObject();
7580 mVRDPServer->init (this, aMachine->mVRDPServer);
7581#endif
7582 /* create another DVD drive object that will be mutable */
7583 unconst (mDVDDrive).createObject();
7584 mDVDDrive->init (this, aMachine->mDVDDrive);
7585 /* create another floppy drive object that will be mutable */
7586 unconst (mFloppyDrive).createObject();
7587 mFloppyDrive->init (this, aMachine->mFloppyDrive);
7588 /* create another audio adapter object that will be mutable */
7589 unconst (mAudioAdapter).createObject();
7590 mAudioAdapter->init (this, aMachine->mAudioAdapter);
7591 /* create a list of serial ports that will be mutable */
7592 for (ULONG slot = 0; slot < ELEMENTS (mSerialPorts); slot ++)
7593 {
7594 unconst (mSerialPorts [slot]).createObject();
7595 mSerialPorts [slot]->init (this, aMachine->mSerialPorts [slot]);
7596 }
7597 /* create a list of parallel ports that will be mutable */
7598 for (ULONG slot = 0; slot < ELEMENTS (mParallelPorts); slot ++)
7599 {
7600 unconst (mParallelPorts [slot]).createObject();
7601 mParallelPorts [slot]->init (this, aMachine->mParallelPorts [slot]);
7602 }
7603 /* create another USB controller object that will be mutable */
7604 unconst (mUSBController).createObject();
7605 mUSBController->init (this, aMachine->mUSBController);
7606 /* create another SATA controller object that will be mutable */
7607 unconst (mSATAController).createObject();
7608 mSATAController->init (this, aMachine->mSATAController);
7609 /* create a list of network adapters that will be mutable */
7610 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
7611 {
7612 unconst (mNetworkAdapters [slot]).createObject();
7613 mNetworkAdapters [slot]->init (this, aMachine->mNetworkAdapters [slot]);
7614 }
7615
7616 /* Confirm a successful initialization when it's the case */
7617 autoInitSpan.setSucceeded();
7618
7619 LogFlowThisFuncLeave();
7620 return S_OK;
7621}
7622
7623/**
7624 * Uninitializes this session object. If the reason is other than
7625 * Uninit::Unexpected, then this method MUST be called from #checkForDeath().
7626 *
7627 * @param aReason uninitialization reason
7628 *
7629 * @note Locks mParent + this object for writing.
7630 */
7631void SessionMachine::uninit (Uninit::Reason aReason)
7632{
7633 LogFlowThisFuncEnter();
7634 LogFlowThisFunc (("reason=%d\n", aReason));
7635
7636 /*
7637 * Strongly reference ourselves to prevent this object deletion after
7638 * mData->mSession.mMachine.setNull() below (which can release the last
7639 * reference and call the destructor). Important: this must be done before
7640 * accessing any members (and before AutoUninitSpan that does it as well).
7641 * This self reference will be released as the very last step on return.
7642 */
7643 ComObjPtr <SessionMachine> selfRef = this;
7644
7645 /* Enclose the state transition Ready->InUninit->NotReady */
7646 AutoUninitSpan autoUninitSpan (this);
7647 if (autoUninitSpan.uninitDone())
7648 {
7649 LogFlowThisFunc (("Already uninitialized\n"));
7650 LogFlowThisFuncLeave();
7651 return;
7652 }
7653
7654 if (autoUninitSpan.initFailed())
7655 {
7656 /* We've been called by init() because it's failed. It's not really
7657 * necessary (nor it's safe) to perform the regular uninit sequense
7658 * below, the following is enough.
7659 */
7660 LogFlowThisFunc (("Initialization failed.\n"));
7661#if defined(RT_OS_WINDOWS)
7662 if (mIPCSem)
7663 ::CloseHandle (mIPCSem);
7664 mIPCSem = NULL;
7665#elif defined(RT_OS_OS2)
7666 if (mIPCSem != NULLHANDLE)
7667 ::DosCloseMutexSem (mIPCSem);
7668 mIPCSem = NULLHANDLE;
7669#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
7670 if (mIPCSem >= 0)
7671 ::semctl (mIPCSem, 0, IPC_RMID);
7672 mIPCSem = -1;
7673#else
7674# error "Port me!"
7675#endif
7676 uninitDataAndChildObjects();
7677 mData.free();
7678 unconst (mParent).setNull();
7679 unconst (mPeer).setNull();
7680 LogFlowThisFuncLeave();
7681 return;
7682 }
7683
7684 /* We need to lock this object in uninit() because the lock is shared
7685 * with mPeer (as well as data we modify below). mParent->addProcessToReap()
7686 * and others need mParent lock. */
7687 AutoMultiWriteLock2 alock (mParent, this);
7688
7689 MachineState_T lastState = mData->mMachineState;
7690
7691 if (aReason == Uninit::Abnormal)
7692 {
7693 LogWarningThisFunc (("ABNORMAL client termination! (wasRunning=%d)\n",
7694 lastState >= MachineState_Running));
7695
7696 /* reset the state to Aborted */
7697 if (mData->mMachineState != MachineState_Aborted)
7698 setMachineState (MachineState_Aborted);
7699 }
7700
7701 if (isModified())
7702 {
7703 LogWarningThisFunc (("Discarding unsaved settings changes!\n"));
7704 rollback (false /* aNotify */);
7705 }
7706
7707 Assert (!mSnapshotData.mStateFilePath || !mSnapshotData.mSnapshot);
7708 if (mSnapshotData.mStateFilePath)
7709 {
7710 LogWarningThisFunc (("canceling failed save state request!\n"));
7711 endSavingState (FALSE /* aSuccess */);
7712 }
7713 else if (!!mSnapshotData.mSnapshot)
7714 {
7715 LogWarningThisFunc (("canceling untaken snapshot!\n"));
7716 endTakingSnapshot (FALSE /* aSuccess */);
7717 }
7718
7719#ifdef VBOX_WITH_USB
7720 /* release all captured USB devices */
7721 if (aReason == Uninit::Abnormal && lastState >= MachineState_Running)
7722 {
7723 /* Console::captureUSBDevices() is called in the VM process only after
7724 * setting the machine state to Starting or Restoring.
7725 * Console::detachAllUSBDevices() will be called upon successful
7726 * termination. So, we need to release USB devices only if there was
7727 * an abnormal termination of a running VM.
7728 *
7729 * This is identical to SessionMachine::DetachAllUSBDevices except
7730 * for the aAbnormal argument. */
7731 HRESULT rc = mUSBController->notifyProxy (false /* aInsertFilters */);
7732 AssertComRC (rc);
7733 NOREF (rc);
7734
7735 USBProxyService *service = mParent->host()->usbProxyService();
7736 if (service)
7737 service->detachAllDevicesFromVM (this, true /* aDone */, true /* aAbnormal */);
7738 }
7739#endif /* VBOX_WITH_USB */
7740
7741 if (!mData->mSession.mType.isNull())
7742 {
7743 /* mType is not null when this machine's process has been started by
7744 * VirtualBox::OpenRemoteSession(), therefore it is our child. We
7745 * need to queue the PID to reap the process (and avoid zombies on
7746 * Linux). */
7747 Assert (mData->mSession.mPid != NIL_RTPROCESS);
7748 mParent->addProcessToReap (mData->mSession.mPid);
7749 }
7750
7751 mData->mSession.mPid = NIL_RTPROCESS;
7752
7753 if (aReason == Uninit::Unexpected)
7754 {
7755 /* Uninitialization didn't come from #checkForDeath(), so tell the
7756 * client watcher thread to update the set of machines that have open
7757 * sessions. */
7758 mParent->updateClientWatcher();
7759 }
7760
7761 /* uninitialize all remote controls */
7762 if (mData->mSession.mRemoteControls.size())
7763 {
7764 LogFlowThisFunc (("Closing remote sessions (%d):\n",
7765 mData->mSession.mRemoteControls.size()));
7766
7767 Data::Session::RemoteControlList::iterator it =
7768 mData->mSession.mRemoteControls.begin();
7769 while (it != mData->mSession.mRemoteControls.end())
7770 {
7771 LogFlowThisFunc ((" Calling remoteControl->Uninitialize()...\n"));
7772 HRESULT rc = (*it)->Uninitialize();
7773 LogFlowThisFunc ((" remoteControl->Uninitialize() returned %08X\n", rc));
7774 if (FAILED (rc))
7775 LogWarningThisFunc (("Forgot to close the remote session?\n"));
7776 ++ it;
7777 }
7778 mData->mSession.mRemoteControls.clear();
7779 }
7780
7781 /*
7782 * An expected uninitialization can come only from #checkForDeath().
7783 * Otherwise it means that something's got really wrong (for examlple,
7784 * the Session implementation has released the VirtualBox reference
7785 * before it triggered #OnSessionEnd(), or before releasing IPC semaphore,
7786 * etc). However, it's also possible, that the client releases the IPC
7787 * semaphore correctly (i.e. before it releases the VirtualBox reference),
7788 * but but the VirtualBox release event comes first to the server process.
7789 * This case is practically possible, so we should not assert on an
7790 * unexpected uninit, just log a warning.
7791 */
7792
7793 if ((aReason == Uninit::Unexpected))
7794 LogWarningThisFunc (("Unexpected SessionMachine uninitialization!\n"));
7795
7796 if (aReason != Uninit::Normal)
7797 {
7798 mData->mSession.mDirectControl.setNull();
7799 }
7800 else
7801 {
7802 /* this must be null here (see #OnSessionEnd()) */
7803 Assert (mData->mSession.mDirectControl.isNull());
7804 Assert (mData->mSession.mState == SessionState_Closing);
7805 Assert (!mData->mSession.mProgress.isNull());
7806
7807 mData->mSession.mProgress->notifyComplete (S_OK);
7808 mData->mSession.mProgress.setNull();
7809 }
7810
7811 /* remove the association between the peer machine and this session machine */
7812 Assert (mData->mSession.mMachine == this ||
7813 aReason == Uninit::Unexpected);
7814
7815 /* reset the rest of session data */
7816 mData->mSession.mMachine.setNull();
7817 mData->mSession.mState = SessionState_Closed;
7818 mData->mSession.mType.setNull();
7819
7820 /* close the interprocess semaphore before leaving the shared lock */
7821#if defined(RT_OS_WINDOWS)
7822 if (mIPCSem)
7823 ::CloseHandle (mIPCSem);
7824 mIPCSem = NULL;
7825#elif defined(RT_OS_OS2)
7826 if (mIPCSem != NULLHANDLE)
7827 ::DosCloseMutexSem (mIPCSem);
7828 mIPCSem = NULLHANDLE;
7829#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
7830 if (mIPCSem >= 0)
7831 ::semctl (mIPCSem, 0, IPC_RMID);
7832 mIPCSem = -1;
7833#else
7834# error "Port me!"
7835#endif
7836
7837 /* fire an event */
7838 mParent->onSessionStateChange (mData->mUuid, SessionState_Closed);
7839
7840 uninitDataAndChildObjects();
7841
7842 /* free the essential data structure last */
7843 mData.free();
7844
7845 /* leave the shared lock before setting the below two to NULL */
7846 alock.leave();
7847
7848 unconst (mParent).setNull();
7849 unconst (mPeer).setNull();
7850
7851 LogFlowThisFuncLeave();
7852}
7853
7854// util::Lockable interface
7855////////////////////////////////////////////////////////////////////////////////
7856
7857/**
7858 * Overrides VirtualBoxBase::lockHandle() in order to share the lock handle
7859 * with the primary Machine instance (mPeer).
7860 */
7861RWLockHandle *SessionMachine::lockHandle() const
7862{
7863 AssertReturn (!mPeer.isNull(), NULL);
7864 return mPeer->lockHandle();
7865}
7866
7867// IInternalMachineControl methods
7868////////////////////////////////////////////////////////////////////////////////
7869
7870/**
7871 * @note Locks the same as #setMachineState() does.
7872 */
7873STDMETHODIMP SessionMachine::UpdateState (MachineState_T machineState)
7874{
7875 return setMachineState (machineState);
7876}
7877
7878/**
7879 * @note Locks this object for reading.
7880 */
7881STDMETHODIMP SessionMachine::GetIPCId (BSTR *id)
7882{
7883 AutoCaller autoCaller (this);
7884 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7885
7886 AutoReadLock alock (this);
7887
7888#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
7889 mIPCSemName.cloneTo (id);
7890 return S_OK;
7891#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
7892 mData->mConfigFileFull.cloneTo (id);
7893 return S_OK;
7894#else
7895# error "Port me!"
7896#endif
7897}
7898
7899/**
7900 * Goes through the USB filters of the given machine to see if the given
7901 * device matches any filter or not.
7902 *
7903 * @note Locks the same as USBController::hasMatchingFilter() does.
7904 */
7905STDMETHODIMP SessionMachine::RunUSBDeviceFilters (IUSBDevice *aUSBDevice,
7906 BOOL *aMatched,
7907 ULONG *aMaskedIfs)
7908{
7909 LogFlowThisFunc (("\n"));
7910
7911 if (!aUSBDevice)
7912 return E_INVALIDARG;
7913 if (!aMatched)
7914 return E_POINTER;
7915
7916 AutoCaller autoCaller (this);
7917 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7918
7919#ifdef VBOX_WITH_USB
7920 *aMatched = mUSBController->hasMatchingFilter (aUSBDevice, aMaskedIfs);
7921#else
7922 *aMatched = FALSE;
7923#endif
7924
7925 return S_OK;
7926}
7927
7928/**
7929 * @note Locks the same as Host::captureUSBDevice() does.
7930 */
7931STDMETHODIMP SessionMachine::CaptureUSBDevice (INPTR GUIDPARAM aId)
7932{
7933 LogFlowThisFunc (("\n"));
7934
7935 AutoCaller autoCaller (this);
7936 AssertComRCReturnRC (autoCaller.rc());
7937
7938#ifdef VBOX_WITH_USB
7939 /* if captureDeviceForVM() fails, it must have set extended error info */
7940 MultiResult rc = mParent->host()->checkUSBProxyService();
7941 CheckComRCReturnRC (rc);
7942
7943 USBProxyService *service = mParent->host()->usbProxyService();
7944 AssertReturn (service, E_FAIL);
7945 return service->captureDeviceForVM (this, aId);
7946#else
7947 return E_FAIL;
7948#endif
7949}
7950
7951/**
7952 * @note Locks the same as Host::detachUSBDevice() does.
7953 */
7954STDMETHODIMP SessionMachine::DetachUSBDevice (INPTR GUIDPARAM aId, BOOL aDone)
7955{
7956 LogFlowThisFunc (("\n"));
7957
7958 AutoCaller autoCaller (this);
7959 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7960
7961#ifdef VBOX_WITH_USB
7962 USBProxyService *service = mParent->host()->usbProxyService();
7963 AssertReturn (service, E_FAIL);
7964 return service->detachDeviceFromVM (this, aId, !!aDone);
7965#else
7966 return E_FAIL;
7967#endif
7968}
7969
7970/**
7971 * Inserts all machine filters to the USB proxy service and then calls
7972 * Host::autoCaptureUSBDevices().
7973 *
7974 * Called by Console from the VM process upon VM startup.
7975 *
7976 * @note Locks what called methods lock.
7977 */
7978STDMETHODIMP SessionMachine::AutoCaptureUSBDevices()
7979{
7980 LogFlowThisFunc (("\n"));
7981
7982 AutoCaller autoCaller (this);
7983 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7984
7985#ifdef VBOX_WITH_USB
7986 HRESULT rc = mUSBController->notifyProxy (true /* aInsertFilters */);
7987 AssertComRC (rc);
7988 NOREF (rc);
7989
7990 USBProxyService *service = mParent->host()->usbProxyService();
7991 AssertReturn (service, E_FAIL);
7992 return service->autoCaptureDevicesForVM (this);
7993#else
7994 return S_OK;
7995#endif
7996}
7997
7998/**
7999 * Removes all machine filters from the USB proxy service and then calls
8000 * Host::detachAllUSBDevices().
8001 *
8002 * Called by Console from the VM process upon normal VM termination or by
8003 * SessionMachine::uninit() upon abnormal VM termination (from under the
8004 * Machine/SessionMachine lock).
8005 *
8006 * @note Locks what called methods lock.
8007 */
8008STDMETHODIMP SessionMachine::DetachAllUSBDevices(BOOL aDone)
8009{
8010 LogFlowThisFunc (("\n"));
8011
8012 AutoCaller autoCaller (this);
8013 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8014
8015#ifdef VBOX_WITH_USB
8016 HRESULT rc = mUSBController->notifyProxy (false /* aInsertFilters */);
8017 AssertComRC (rc);
8018 NOREF (rc);
8019
8020 USBProxyService *service = mParent->host()->usbProxyService();
8021 AssertReturn (service, E_FAIL);
8022 return service->detachAllDevicesFromVM (this, !!aDone, false /* aAbnormal */);
8023#else
8024 return S_OK;
8025#endif
8026}
8027
8028/**
8029 * @note Locks mParent + this object for writing.
8030 */
8031STDMETHODIMP SessionMachine::OnSessionEnd (ISession *aSession,
8032 IProgress **aProgress)
8033{
8034 LogFlowThisFuncEnter();
8035
8036 AssertReturn (aSession, E_INVALIDARG);
8037 AssertReturn (aProgress, E_INVALIDARG);
8038
8039 AutoCaller autoCaller (this);
8040
8041 LogFlowThisFunc (("state=%d\n", autoCaller.state()));
8042 /*
8043 * We don't assert below because it might happen that a non-direct session
8044 * informs us it is closed right after we've been uninitialized -- it's ok.
8045 */
8046 CheckComRCReturnRC (autoCaller.rc());
8047
8048 /* get IInternalSessionControl interface */
8049 ComPtr <IInternalSessionControl> control (aSession);
8050
8051 ComAssertRet (!control.isNull(), E_INVALIDARG);
8052
8053 /* Progress::init() needs mParent lock */
8054 AutoMultiWriteLock2 alock (mParent, this);
8055
8056 if (control.equalsTo (mData->mSession.mDirectControl))
8057 {
8058 ComAssertRet (aProgress, E_POINTER);
8059
8060 /* The direct session is being normally closed by the client process
8061 * ----------------------------------------------------------------- */
8062
8063 /* go to the closing state (essential for all open*Session() calls and
8064 * for #checkForDeath()) */
8065 Assert (mData->mSession.mState == SessionState_Open);
8066 mData->mSession.mState = SessionState_Closing;
8067
8068 /* set direct control to NULL to release the remote instance */
8069 mData->mSession.mDirectControl.setNull();
8070 LogFlowThisFunc (("Direct control is set to NULL\n"));
8071
8072 /*
8073 * Create the progress object the client will use to wait until
8074 * #checkForDeath() is called to uninitialize this session object
8075 * after it releases the IPC semaphore.
8076 */
8077 ComObjPtr <Progress> progress;
8078 progress.createObject();
8079 progress->init (mParent, static_cast <IMachine *> (mPeer),
8080 Bstr (tr ("Closing session")), FALSE /* aCancelable */);
8081 progress.queryInterfaceTo (aProgress);
8082 mData->mSession.mProgress = progress;
8083 }
8084 else
8085 {
8086 /* the remote session is being normally closed */
8087 Data::Session::RemoteControlList::iterator it =
8088 mData->mSession.mRemoteControls.begin();
8089 while (it != mData->mSession.mRemoteControls.end())
8090 {
8091 if (control.equalsTo (*it))
8092 break;
8093 ++it;
8094 }
8095 BOOL found = it != mData->mSession.mRemoteControls.end();
8096 ComAssertMsgRet (found, ("The session is not found in the session list!"),
8097 E_INVALIDARG);
8098 mData->mSession.mRemoteControls.remove (*it);
8099 }
8100
8101 LogFlowThisFuncLeave();
8102 return S_OK;
8103}
8104
8105/**
8106 * @note Locks mParent + this object for writing.
8107 */
8108STDMETHODIMP SessionMachine::BeginSavingState (IProgress *aProgress, BSTR *aStateFilePath)
8109{
8110 LogFlowThisFuncEnter();
8111
8112 AssertReturn (aProgress, E_INVALIDARG);
8113 AssertReturn (aStateFilePath, E_POINTER);
8114
8115 AutoCaller autoCaller (this);
8116 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8117
8118 /* mParent->addProgress() needs mParent lock */
8119 AutoMultiWriteLock2 alock (mParent, this);
8120
8121 AssertReturn (mData->mMachineState == MachineState_Paused &&
8122 mSnapshotData.mLastState == MachineState_Null &&
8123 mSnapshotData.mProgressId.isEmpty() &&
8124 mSnapshotData.mStateFilePath.isNull(),
8125 E_FAIL);
8126
8127 /* memorize the progress ID and add it to the global collection */
8128 Guid progressId;
8129 HRESULT rc = aProgress->COMGETTER(Id) (progressId.asOutParam());
8130 AssertComRCReturn (rc, rc);
8131 rc = mParent->addProgress (aProgress);
8132 AssertComRCReturn (rc, rc);
8133
8134 Bstr stateFilePath;
8135 /* stateFilePath is null when the machine is not running */
8136 if (mData->mMachineState == MachineState_Paused)
8137 {
8138 stateFilePath = Utf8StrFmt ("%ls%c{%Vuuid}.sav",
8139 mUserData->mSnapshotFolderFull.raw(),
8140 RTPATH_DELIMITER, mData->mUuid.raw());
8141 }
8142
8143 /* fill in the snapshot data */
8144 mSnapshotData.mLastState = mData->mMachineState;
8145 mSnapshotData.mProgressId = progressId;
8146 mSnapshotData.mStateFilePath = stateFilePath;
8147
8148 /* set the state to Saving (this is expected by Console::SaveState()) */
8149 setMachineState (MachineState_Saving);
8150
8151 stateFilePath.cloneTo (aStateFilePath);
8152
8153 return S_OK;
8154}
8155
8156/**
8157 * @note Locks mParent + this objects for writing.
8158 */
8159STDMETHODIMP SessionMachine::EndSavingState (BOOL aSuccess)
8160{
8161 LogFlowThisFunc (("\n"));
8162
8163 AutoCaller autoCaller (this);
8164 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8165
8166 /* endSavingState() need mParent lock */
8167 AutoMultiWriteLock2 alock (mParent, this);
8168
8169 AssertReturn (mData->mMachineState == MachineState_Saving &&
8170 mSnapshotData.mLastState != MachineState_Null &&
8171 !mSnapshotData.mProgressId.isEmpty() &&
8172 !mSnapshotData.mStateFilePath.isNull(),
8173 E_FAIL);
8174
8175 /*
8176 * on success, set the state to Saved;
8177 * on failure, set the state to the state we had when BeginSavingState() was
8178 * called (this is expected by Console::SaveState() and
8179 * Console::saveStateThread())
8180 */
8181 if (aSuccess)
8182 setMachineState (MachineState_Saved);
8183 else
8184 setMachineState (mSnapshotData.mLastState);
8185
8186 return endSavingState (aSuccess);
8187}
8188
8189/**
8190 * @note Locks this objects for writing.
8191 */
8192STDMETHODIMP SessionMachine::AdoptSavedState (INPTR BSTR aSavedStateFile)
8193{
8194 LogFlowThisFunc (("\n"));
8195
8196 AssertReturn (aSavedStateFile, E_INVALIDARG);
8197
8198 AutoCaller autoCaller (this);
8199 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8200
8201 AutoWriteLock alock (this);
8202
8203 AssertReturn (mData->mMachineState == MachineState_PoweredOff ||
8204 mData->mMachineState == MachineState_Aborted,
8205 E_FAIL);
8206
8207 Utf8Str stateFilePathFull = aSavedStateFile;
8208 int vrc = calculateFullPath (stateFilePathFull, stateFilePathFull);
8209 if (VBOX_FAILURE (vrc))
8210 return setError (E_FAIL,
8211 tr ("Invalid saved state file path: '%ls' (%Vrc)"),
8212 aSavedStateFile, vrc);
8213
8214 mSSData->mStateFilePath = stateFilePathFull;
8215
8216 /* The below setMachineState() will detect the state transition and will
8217 * update the settings file */
8218
8219 return setMachineState (MachineState_Saved);
8220}
8221
8222/**
8223 * @note Locks mParent + this objects for writing.
8224 */
8225STDMETHODIMP SessionMachine::BeginTakingSnapshot (
8226 IConsole *aInitiator, INPTR BSTR aName, INPTR BSTR aDescription,
8227 IProgress *aProgress, BSTR *aStateFilePath,
8228 IProgress **aServerProgress)
8229{
8230 LogFlowThisFuncEnter();
8231
8232 AssertReturn (aInitiator && aName, E_INVALIDARG);
8233 AssertReturn (aStateFilePath && aServerProgress, E_POINTER);
8234
8235 LogFlowThisFunc (("aName='%ls'\n", aName));
8236
8237 AutoCaller autoCaller (this);
8238 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8239
8240 /* Progress::init() needs mParent lock */
8241 AutoMultiWriteLock2 alock (mParent, this);
8242
8243 AssertReturn ((mData->mMachineState < MachineState_Running ||
8244 mData->mMachineState == MachineState_Paused) &&
8245 mSnapshotData.mLastState == MachineState_Null &&
8246 mSnapshotData.mSnapshot.isNull() &&
8247 mSnapshotData.mServerProgress.isNull() &&
8248 mSnapshotData.mCombinedProgress.isNull(),
8249 E_FAIL);
8250
8251 bool takingSnapshotOnline = mData->mMachineState == MachineState_Paused;
8252
8253 if (!takingSnapshotOnline && mData->mMachineState != MachineState_Saved)
8254 {
8255 /*
8256 * save all current settings to ensure current changes are committed
8257 * and hard disks are fixed up
8258 */
8259 HRESULT rc = saveSettings();
8260 CheckComRCReturnRC (rc);
8261 }
8262
8263 /* check that there are no Writethrough hard disks attached */
8264 for (HDData::HDAttachmentList::const_iterator
8265 it = mHDData->mHDAttachments.begin();
8266 it != mHDData->mHDAttachments.end();
8267 ++ it)
8268 {
8269 ComObjPtr <HardDisk> hd = (*it)->hardDisk();
8270 AutoWriteLock hdLock (hd);
8271 if (hd->type() == HardDiskType_Writethrough)
8272 return setError (E_FAIL,
8273 tr ("Cannot take a snapshot when there is a Writethrough hard "
8274 " disk attached ('%ls')"), hd->toString().raw());
8275 }
8276
8277 AssertReturn (aProgress || !takingSnapshotOnline, E_FAIL);
8278
8279 /* create an ID for the snapshot */
8280 Guid snapshotId;
8281 snapshotId.create();
8282
8283 Bstr stateFilePath;
8284 /* stateFilePath is null when the machine is not online nor saved */
8285 if (takingSnapshotOnline || mData->mMachineState == MachineState_Saved)
8286 stateFilePath = Utf8StrFmt ("%ls%c{%Vuuid}.sav",
8287 mUserData->mSnapshotFolderFull.raw(),
8288 RTPATH_DELIMITER,
8289 snapshotId.ptr());
8290
8291 /* ensure the directory for the saved state file exists */
8292 if (stateFilePath)
8293 {
8294 Utf8Str dir = stateFilePath;
8295 RTPathStripFilename (dir.mutableRaw());
8296 if (!RTDirExists (dir))
8297 {
8298 int vrc = RTDirCreateFullPath (dir, 0777);
8299 if (VBOX_FAILURE (vrc))
8300 return setError (E_FAIL,
8301 tr ("Could not create a directory '%s' to save the "
8302 "VM state to (%Vrc)"),
8303 dir.raw(), vrc);
8304 }
8305 }
8306
8307 /* create a snapshot machine object */
8308 ComObjPtr <SnapshotMachine> snapshotMachine;
8309 snapshotMachine.createObject();
8310 HRESULT rc = snapshotMachine->init (this, snapshotId, stateFilePath);
8311 AssertComRCReturn (rc, rc);
8312
8313 Bstr progressDesc = Bstr (tr ("Taking snapshot of virtual machine"));
8314 Bstr firstOpDesc = Bstr (tr ("Preparing to take snapshot"));
8315
8316 /*
8317 * create a server-side progress object (it will be descriptionless
8318 * when we need to combine it with the VM-side progress, i.e. when we're
8319 * taking a snapshot online). The number of operations is:
8320 * 1 (preparing) + # of VDIs + 1 (if the state is saved so we need to copy it)
8321 */
8322 ComObjPtr <Progress> serverProgress;
8323 {
8324 ULONG opCount = 1 + mHDData->mHDAttachments.size();
8325 if (mData->mMachineState == MachineState_Saved)
8326 opCount ++;
8327 serverProgress.createObject();
8328 if (takingSnapshotOnline)
8329 rc = serverProgress->init (FALSE, opCount, firstOpDesc);
8330 else
8331 rc = serverProgress->init (mParent, aInitiator, progressDesc, FALSE,
8332 opCount, firstOpDesc);
8333 AssertComRCReturn (rc, rc);
8334 }
8335
8336 /* create a combined server-side progress object when necessary */
8337 ComObjPtr <CombinedProgress> combinedProgress;
8338 if (takingSnapshotOnline)
8339 {
8340 combinedProgress.createObject();
8341 rc = combinedProgress->init (mParent, aInitiator, progressDesc,
8342 serverProgress, aProgress);
8343 AssertComRCReturn (rc, rc);
8344 }
8345
8346 /* create a snapshot object */
8347 RTTIMESPEC time;
8348 ComObjPtr <Snapshot> snapshot;
8349 snapshot.createObject();
8350 rc = snapshot->init (snapshotId, aName, aDescription,
8351 *RTTimeNow (&time), snapshotMachine,
8352 mData->mCurrentSnapshot);
8353 AssertComRCReturnRC (rc);
8354
8355 /*
8356 * create and start the task on a separate thread
8357 * (note that it will not start working until we release alock)
8358 */
8359 TakeSnapshotTask *task = new TakeSnapshotTask (this);
8360 int vrc = RTThreadCreate (NULL, taskHandler,
8361 (void *) task,
8362 0, RTTHREADTYPE_MAIN_WORKER, 0, "TakeSnapshot");
8363 if (VBOX_FAILURE (vrc))
8364 {
8365 snapshot->uninit();
8366 delete task;
8367 ComAssertFailedRet (E_FAIL);
8368 }
8369
8370 /* fill in the snapshot data */
8371 mSnapshotData.mLastState = mData->mMachineState;
8372 mSnapshotData.mSnapshot = snapshot;
8373 mSnapshotData.mServerProgress = serverProgress;
8374 mSnapshotData.mCombinedProgress = combinedProgress;
8375
8376 /* set the state to Saving (this is expected by Console::TakeSnapshot()) */
8377 setMachineState (MachineState_Saving);
8378
8379 if (takingSnapshotOnline)
8380 stateFilePath.cloneTo (aStateFilePath);
8381 else
8382 *aStateFilePath = NULL;
8383
8384 serverProgress.queryInterfaceTo (aServerProgress);
8385
8386 LogFlowThisFuncLeave();
8387 return S_OK;
8388}
8389
8390/**
8391 * @note Locks mParent + this objects for writing.
8392 */
8393STDMETHODIMP SessionMachine::EndTakingSnapshot (BOOL aSuccess)
8394{
8395 LogFlowThisFunc (("\n"));
8396
8397 AutoCaller autoCaller (this);
8398 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8399
8400 /* Lock mParent because of endTakingSnapshot() */
8401 AutoMultiWriteLock2 alock (mParent, this);
8402
8403 AssertReturn (!aSuccess ||
8404 (mData->mMachineState == MachineState_Saving &&
8405 mSnapshotData.mLastState != MachineState_Null &&
8406 !mSnapshotData.mSnapshot.isNull() &&
8407 !mSnapshotData.mServerProgress.isNull() &&
8408 !mSnapshotData.mCombinedProgress.isNull()),
8409 E_FAIL);
8410
8411 /*
8412 * set the state to the state we had when BeginTakingSnapshot() was called
8413 * (this is expected by Console::TakeSnapshot() and
8414 * Console::saveStateThread())
8415 */
8416 setMachineState (mSnapshotData.mLastState);
8417
8418 return endTakingSnapshot (aSuccess);
8419}
8420
8421/**
8422 * @note Locks mParent + this + children objects for writing!
8423 */
8424STDMETHODIMP SessionMachine::DiscardSnapshot (
8425 IConsole *aInitiator, INPTR GUIDPARAM aId,
8426 MachineState_T *aMachineState, IProgress **aProgress)
8427{
8428 LogFlowThisFunc (("\n"));
8429
8430 Guid id = aId;
8431 AssertReturn (aInitiator && !id.isEmpty(), E_INVALIDARG);
8432 AssertReturn (aMachineState && aProgress, E_POINTER);
8433
8434 AutoCaller autoCaller (this);
8435 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8436
8437 /* Progress::init() needs mParent lock */
8438 AutoMultiWriteLock2 alock (mParent, this);
8439
8440 ComAssertRet (mData->mMachineState < MachineState_Running, E_FAIL);
8441
8442 ComObjPtr <Snapshot> snapshot;
8443 HRESULT rc = findSnapshot (id, snapshot, true /* aSetError */);
8444 CheckComRCReturnRC (rc);
8445
8446 AutoWriteLock snapshotLock (snapshot);
8447 if (snapshot == mData->mFirstSnapshot)
8448 {
8449 AutoWriteLock chLock (mData->mFirstSnapshot->childrenLock ());
8450 size_t childrenCount = mData->mFirstSnapshot->children().size();
8451 if (childrenCount > 1)
8452 return setError (E_FAIL,
8453 tr ("Cannot discard the snapshot '%ls' because it is the first "
8454 "snapshot of the machine '%ls' and it has more than one "
8455 "child snapshot (%d)"),
8456 snapshot->data().mName.raw(), mUserData->mName.raw(),
8457 childrenCount);
8458 }
8459
8460 /*
8461 * If the snapshot being discarded is the current one, ensure current
8462 * settings are committed and saved.
8463 */
8464 if (snapshot == mData->mCurrentSnapshot)
8465 {
8466 if (isModified())
8467 {
8468 rc = saveSettings();
8469 CheckComRCReturnRC (rc);
8470 }
8471 }
8472
8473 /*
8474 * create a progress object. The number of operations is:
8475 * 1 (preparing) + # of VDIs
8476 */
8477 ComObjPtr <Progress> progress;
8478 progress.createObject();
8479 rc = progress->init (mParent, aInitiator,
8480 Bstr (Utf8StrFmt (tr ("Discarding snapshot '%ls'"),
8481 snapshot->data().mName.raw())),
8482 FALSE /* aCancelable */,
8483 1 + snapshot->data().mMachine->mHDData->mHDAttachments.size(),
8484 Bstr (tr ("Preparing to discard snapshot")));
8485 AssertComRCReturn (rc, rc);
8486
8487 /* create and start the task on a separate thread */
8488 DiscardSnapshotTask *task = new DiscardSnapshotTask (this, progress, snapshot);
8489 int vrc = RTThreadCreate (NULL, taskHandler,
8490 (void *) task,
8491 0, RTTHREADTYPE_MAIN_WORKER, 0, "DiscardSnapshot");
8492 if (VBOX_FAILURE (vrc))
8493 delete task;
8494 ComAssertRCRet (vrc, E_FAIL);
8495
8496 /* set the proper machine state (note: after creating a Task instance) */
8497 setMachineState (MachineState_Discarding);
8498
8499 /* return the progress to the caller */
8500 progress.queryInterfaceTo (aProgress);
8501
8502 /* return the new state to the caller */
8503 *aMachineState = mData->mMachineState;
8504
8505 return S_OK;
8506}
8507
8508/**
8509 * @note Locks mParent + this + children objects for writing!
8510 */
8511STDMETHODIMP SessionMachine::DiscardCurrentState (
8512 IConsole *aInitiator, MachineState_T *aMachineState, IProgress **aProgress)
8513{
8514 LogFlowThisFunc (("\n"));
8515
8516 AssertReturn (aInitiator, E_INVALIDARG);
8517 AssertReturn (aMachineState && aProgress, E_POINTER);
8518
8519 AutoCaller autoCaller (this);
8520 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8521
8522 /* Progress::init() needs mParent lock */
8523 AutoMultiWriteLock2 alock (mParent, this);
8524
8525 ComAssertRet (mData->mMachineState < MachineState_Running, E_FAIL);
8526
8527 if (mData->mCurrentSnapshot.isNull())
8528 return setError (E_FAIL,
8529 tr ("Could not discard the current state of the machine '%ls' "
8530 "because it doesn't have any snapshots"),
8531 mUserData->mName.raw());
8532
8533 /*
8534 * create a progress object. The number of operations is:
8535 * 1 (preparing) + # of VDIs + 1 (if we need to copy the saved state file)
8536 */
8537 ComObjPtr <Progress> progress;
8538 progress.createObject();
8539 {
8540 ULONG opCount = 1 + mData->mCurrentSnapshot->data()
8541 .mMachine->mHDData->mHDAttachments.size();
8542 if (mData->mCurrentSnapshot->stateFilePath())
8543 ++ opCount;
8544 progress->init (mParent, aInitiator,
8545 Bstr (tr ("Discarding current machine state")),
8546 FALSE /* aCancelable */, opCount,
8547 Bstr (tr ("Preparing to discard current state")));
8548 }
8549
8550 /* create and start the task on a separate thread */
8551 DiscardCurrentStateTask *task =
8552 new DiscardCurrentStateTask (this, progress, false /* discardCurSnapshot */);
8553 int vrc = RTThreadCreate (NULL, taskHandler,
8554 (void *) task,
8555 0, RTTHREADTYPE_MAIN_WORKER, 0, "DiscardCurState");
8556 if (VBOX_FAILURE (vrc))
8557 delete task;
8558 ComAssertRCRet (vrc, E_FAIL);
8559
8560 /* set the proper machine state (note: after creating a Task instance) */
8561 setMachineState (MachineState_Discarding);
8562
8563 /* return the progress to the caller */
8564 progress.queryInterfaceTo (aProgress);
8565
8566 /* return the new state to the caller */
8567 *aMachineState = mData->mMachineState;
8568
8569 return S_OK;
8570}
8571
8572/**
8573 * @note Locks mParent + other objects for writing!
8574 */
8575STDMETHODIMP SessionMachine::DiscardCurrentSnapshotAndState (
8576 IConsole *aInitiator, MachineState_T *aMachineState, IProgress **aProgress)
8577{
8578 LogFlowThisFunc (("\n"));
8579
8580 AssertReturn (aInitiator, E_INVALIDARG);
8581 AssertReturn (aMachineState && aProgress, E_POINTER);
8582
8583 AutoCaller autoCaller (this);
8584 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8585
8586 /* Progress::init() needs mParent lock */
8587 AutoMultiWriteLock2 alock (mParent, this);
8588
8589 ComAssertRet (mData->mMachineState < MachineState_Running, E_FAIL);
8590
8591 if (mData->mCurrentSnapshot.isNull())
8592 return setError (E_FAIL,
8593 tr ("Could not discard the current state of the machine '%ls' "
8594 "because it doesn't have any snapshots"),
8595 mUserData->mName.raw());
8596
8597 /*
8598 * create a progress object. The number of operations is:
8599 * 1 (preparing) + # of VDIs in the current snapshot +
8600 * # of VDIs in the previous snapshot +
8601 * 1 (if we need to copy the saved state file of the previous snapshot)
8602 * or (if there is no previous snapshot):
8603 * 1 (preparing) + # of VDIs in the current snapshot * 2 +
8604 * 1 (if we need to copy the saved state file of the current snapshot)
8605 */
8606 ComObjPtr <Progress> progress;
8607 progress.createObject();
8608 {
8609 ComObjPtr <Snapshot> curSnapshot = mData->mCurrentSnapshot;
8610 ComObjPtr <Snapshot> prevSnapshot = mData->mCurrentSnapshot->parent();
8611
8612 ULONG opCount = 1;
8613 if (prevSnapshot)
8614 {
8615 opCount += curSnapshot->data().mMachine->mHDData->mHDAttachments.size();
8616 opCount += prevSnapshot->data().mMachine->mHDData->mHDAttachments.size();
8617 if (prevSnapshot->stateFilePath())
8618 ++ opCount;
8619 }
8620 else
8621 {
8622 opCount += curSnapshot->data().mMachine->mHDData->mHDAttachments.size() * 2;
8623 if (curSnapshot->stateFilePath())
8624 ++ opCount;
8625 }
8626
8627 progress->init (mParent, aInitiator,
8628 Bstr (tr ("Discarding current machine snapshot and state")),
8629 FALSE /* aCancelable */, opCount,
8630 Bstr (tr ("Preparing to discard current snapshot and state")));
8631 }
8632
8633 /* create and start the task on a separate thread */
8634 DiscardCurrentStateTask *task =
8635 new DiscardCurrentStateTask (this, progress, true /* discardCurSnapshot */);
8636 int vrc = RTThreadCreate (NULL, taskHandler,
8637 (void *) task,
8638 0, RTTHREADTYPE_MAIN_WORKER, 0, "DiscardCurState");
8639 if (VBOX_FAILURE (vrc))
8640 delete task;
8641 ComAssertRCRet (vrc, E_FAIL);
8642
8643 /* set the proper machine state (note: after creating a Task instance) */
8644 setMachineState (MachineState_Discarding);
8645
8646 /* return the progress to the caller */
8647 progress.queryInterfaceTo (aProgress);
8648
8649 /* return the new state to the caller */
8650 *aMachineState = mData->mMachineState;
8651
8652 return S_OK;
8653}
8654
8655// public methods only for internal purposes
8656/////////////////////////////////////////////////////////////////////////////
8657
8658/**
8659 * Called from the client watcher thread to check for unexpected client
8660 * process death.
8661 *
8662 * @note On Win32 and on OS/2, this method is called only when we've got the
8663 * mutex (i.e. the client has either died or terminated normally). This
8664 * method always returns true.
8665 *
8666 * @note On Linux, the method returns true if the client process has
8667 * terminated abnormally (and/or the session has been uninitialized) and
8668 * false if it is still alive.
8669 *
8670 * @note Locks this object for writing.
8671 */
8672bool SessionMachine::checkForDeath()
8673{
8674 Uninit::Reason reason;
8675 bool doUninit = false;
8676 bool ret = false;
8677
8678 /*
8679 * Enclose autoCaller with a block because calling uninit()
8680 * from under it will deadlock.
8681 */
8682 {
8683 AutoCaller autoCaller (this);
8684 if (!autoCaller.isOk())
8685 {
8686 /*
8687 * return true if not ready, to cause the client watcher to exclude
8688 * the corresponding session from watching
8689 */
8690 LogFlowThisFunc (("Already uninitialized!"));
8691 return true;
8692 }
8693
8694 AutoWriteLock alock (this);
8695
8696 /*
8697 * Determine the reason of death: if the session state is Closing here,
8698 * everything is fine. Otherwise it means that the client did not call
8699 * OnSessionEnd() before it released the IPC semaphore.
8700 * This may happen either because the client process has abnormally
8701 * terminated, or because it simply forgot to call ISession::Close()
8702 * before exiting. We threat the latter also as an abnormal termination
8703 * (see Session::uninit() for details).
8704 */
8705 reason = mData->mSession.mState == SessionState_Closing ?
8706 Uninit::Normal :
8707 Uninit::Abnormal;
8708
8709#if defined(RT_OS_WINDOWS)
8710
8711 AssertMsg (mIPCSem, ("semaphore must be created"));
8712
8713 /* release the IPC mutex */
8714 ::ReleaseMutex (mIPCSem);
8715
8716 doUninit = true;
8717
8718 ret = true;
8719
8720#elif defined(RT_OS_OS2)
8721
8722 AssertMsg (mIPCSem, ("semaphore must be created"));
8723
8724 /* release the IPC mutex */
8725 ::DosReleaseMutexSem (mIPCSem);
8726
8727 doUninit = true;
8728
8729 ret = true;
8730
8731#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
8732
8733 AssertMsg (mIPCSem >= 0, ("semaphore must be created"));
8734
8735 int val = ::semctl (mIPCSem, 0, GETVAL);
8736 if (val > 0)
8737 {
8738 /* the semaphore is signaled, meaning the session is terminated */
8739 doUninit = true;
8740 }
8741
8742 ret = val > 0;
8743
8744#else
8745# error "Port me!"
8746#endif
8747
8748 } /* AutoCaller block */
8749
8750 if (doUninit)
8751 uninit (reason);
8752
8753 return ret;
8754}
8755
8756/**
8757 * @note Locks this object for reading.
8758 */
8759HRESULT SessionMachine::onDVDDriveChange()
8760{
8761 LogFlowThisFunc (("\n"));
8762
8763 AutoCaller autoCaller (this);
8764 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8765
8766 ComPtr <IInternalSessionControl> directControl;
8767 {
8768 AutoReadLock alock (this);
8769 directControl = mData->mSession.mDirectControl;
8770 }
8771
8772 /* ignore notifications sent after #OnSessionEnd() is called */
8773 if (!directControl)
8774 return S_OK;
8775
8776 return directControl->OnDVDDriveChange();
8777}
8778
8779/**
8780 * @note Locks this object for reading.
8781 */
8782HRESULT SessionMachine::onFloppyDriveChange()
8783{
8784 LogFlowThisFunc (("\n"));
8785
8786 AutoCaller autoCaller (this);
8787 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8788
8789 ComPtr <IInternalSessionControl> directControl;
8790 {
8791 AutoReadLock alock (this);
8792 directControl = mData->mSession.mDirectControl;
8793 }
8794
8795 /* ignore notifications sent after #OnSessionEnd() is called */
8796 if (!directControl)
8797 return S_OK;
8798
8799 return directControl->OnFloppyDriveChange();
8800}
8801
8802/**
8803 * @note Locks this object for reading.
8804 */
8805HRESULT SessionMachine::onNetworkAdapterChange(INetworkAdapter *networkAdapter)
8806{
8807 LogFlowThisFunc (("\n"));
8808
8809 AutoCaller autoCaller (this);
8810 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8811
8812 ComPtr <IInternalSessionControl> directControl;
8813 {
8814 AutoReadLock alock (this);
8815 directControl = mData->mSession.mDirectControl;
8816 }
8817
8818 /* ignore notifications sent after #OnSessionEnd() is called */
8819 if (!directControl)
8820 return S_OK;
8821
8822 return directControl->OnNetworkAdapterChange(networkAdapter);
8823}
8824
8825/**
8826 * @note Locks this object for reading.
8827 */
8828HRESULT SessionMachine::onSerialPortChange(ISerialPort *serialPort)
8829{
8830 LogFlowThisFunc (("\n"));
8831
8832 AutoCaller autoCaller (this);
8833 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8834
8835 ComPtr <IInternalSessionControl> directControl;
8836 {
8837 AutoReadLock alock (this);
8838 directControl = mData->mSession.mDirectControl;
8839 }
8840
8841 /* ignore notifications sent after #OnSessionEnd() is called */
8842 if (!directControl)
8843 return S_OK;
8844
8845 return directControl->OnSerialPortChange(serialPort);
8846}
8847
8848/**
8849 * @note Locks this object for reading.
8850 */
8851HRESULT SessionMachine::onParallelPortChange(IParallelPort *parallelPort)
8852{
8853 LogFlowThisFunc (("\n"));
8854
8855 AutoCaller autoCaller (this);
8856 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8857
8858 ComPtr <IInternalSessionControl> directControl;
8859 {
8860 AutoReadLock alock (this);
8861 directControl = mData->mSession.mDirectControl;
8862 }
8863
8864 /* ignore notifications sent after #OnSessionEnd() is called */
8865 if (!directControl)
8866 return S_OK;
8867
8868 return directControl->OnParallelPortChange(parallelPort);
8869}
8870
8871/**
8872 * @note Locks this object for reading.
8873 */
8874HRESULT SessionMachine::onVRDPServerChange()
8875{
8876 LogFlowThisFunc (("\n"));
8877
8878 AutoCaller autoCaller (this);
8879 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8880
8881 ComPtr <IInternalSessionControl> directControl;
8882 {
8883 AutoReadLock alock (this);
8884 directControl = mData->mSession.mDirectControl;
8885 }
8886
8887 /* ignore notifications sent after #OnSessionEnd() is called */
8888 if (!directControl)
8889 return S_OK;
8890
8891 return directControl->OnVRDPServerChange();
8892}
8893
8894/**
8895 * @note Locks this object for reading.
8896 */
8897HRESULT SessionMachine::onUSBControllerChange()
8898{
8899 LogFlowThisFunc (("\n"));
8900
8901 AutoCaller autoCaller (this);
8902 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8903
8904 ComPtr <IInternalSessionControl> directControl;
8905 {
8906 AutoReadLock alock (this);
8907 directControl = mData->mSession.mDirectControl;
8908 }
8909
8910 /* ignore notifications sent after #OnSessionEnd() is called */
8911 if (!directControl)
8912 return S_OK;
8913
8914 return directControl->OnUSBControllerChange();
8915}
8916
8917/**
8918 * @note Locks this object for reading.
8919 */
8920HRESULT SessionMachine::onSharedFolderChange()
8921{
8922 LogFlowThisFunc (("\n"));
8923
8924 AutoCaller autoCaller (this);
8925 AssertComRCReturnRC (autoCaller.rc());
8926
8927 ComPtr <IInternalSessionControl> directControl;
8928 {
8929 AutoReadLock alock (this);
8930 directControl = mData->mSession.mDirectControl;
8931 }
8932
8933 /* ignore notifications sent after #OnSessionEnd() is called */
8934 if (!directControl)
8935 return S_OK;
8936
8937 return directControl->OnSharedFolderChange (FALSE /* aGlobal */);
8938}
8939
8940/**
8941 * Returns @c true if this machine's USB controller reports it has a matching
8942 * filter for the given USB device and @c false otherwise.
8943 *
8944 * @note Locks this object for reading.
8945 */
8946bool SessionMachine::hasMatchingUSBFilter (const ComObjPtr <HostUSBDevice> &aDevice, ULONG *aMaskedIfs)
8947{
8948 AutoCaller autoCaller (this);
8949 /* silently return if not ready -- this method may be called after the
8950 * direct machine session has been called */
8951 if (!autoCaller.isOk())
8952 return false;
8953
8954 AutoReadLock alock (this);
8955
8956#ifdef VBOX_WITH_USB
8957 switch (mData->mMachineState)
8958 {
8959 case MachineState_Starting:
8960 case MachineState_Restoring:
8961 case MachineState_Paused:
8962 case MachineState_Running:
8963 return mUSBController->hasMatchingFilter (aDevice, aMaskedIfs);
8964 default: break;
8965 }
8966#endif
8967 return false;
8968}
8969
8970/**
8971 * @note The calls shall hold no locks. Will temporarily lock this object for reading.
8972 */
8973HRESULT SessionMachine::onUSBDeviceAttach (IUSBDevice *aDevice,
8974 IVirtualBoxErrorInfo *aError,
8975 ULONG aMaskedIfs)
8976{
8977 LogFlowThisFunc (("\n"));
8978
8979 AutoCaller autoCaller (this);
8980
8981 /* This notification may happen after the machine object has been
8982 * uninitialized (the session was closed), so don't assert. */
8983 CheckComRCReturnRC (autoCaller.rc());
8984
8985 ComPtr <IInternalSessionControl> directControl;
8986 {
8987 AutoReadLock alock (this);
8988 directControl = mData->mSession.mDirectControl;
8989 }
8990
8991 /* fail on notifications sent after #OnSessionEnd() is called, it is
8992 * expected by the caller */
8993 if (!directControl)
8994 return E_FAIL;
8995
8996 /* No locks should be held at this point. */
8997 AssertMsg (RTThreadGetWriteLockCount (RTThreadSelf()) == 0, ("%d\n", RTThreadGetWriteLockCount (RTThreadSelf())));
8998 AssertMsg (RTThreadGetReadLockCount (RTThreadSelf()) == 0, ("%d\n", RTThreadGetReadLockCount (RTThreadSelf())));
8999
9000 return directControl->OnUSBDeviceAttach (aDevice, aError, aMaskedIfs);
9001}
9002
9003/**
9004 * @note The calls shall hold no locks. Will temporarily lock this object for reading.
9005 */
9006HRESULT SessionMachine::onUSBDeviceDetach (INPTR GUIDPARAM aId,
9007 IVirtualBoxErrorInfo *aError)
9008{
9009 LogFlowThisFunc (("\n"));
9010
9011 AutoCaller autoCaller (this);
9012
9013 /* This notification may happen after the machine object has been
9014 * uninitialized (the session was closed), so don't assert. */
9015 CheckComRCReturnRC (autoCaller.rc());
9016
9017 ComPtr <IInternalSessionControl> directControl;
9018 {
9019 AutoReadLock alock (this);
9020 directControl = mData->mSession.mDirectControl;
9021 }
9022
9023 /* fail on notifications sent after #OnSessionEnd() is called, it is
9024 * expected by the caller */
9025 if (!directControl)
9026 return E_FAIL;
9027
9028 /* No locks should be held at this point. */
9029 AssertMsg (RTThreadGetWriteLockCount (RTThreadSelf()) == 0, ("%d\n", RTThreadGetWriteLockCount (RTThreadSelf())));
9030 AssertMsg (RTThreadGetReadLockCount (RTThreadSelf()) == 0, ("%d\n", RTThreadGetReadLockCount (RTThreadSelf())));
9031
9032 return directControl->OnUSBDeviceDetach (aId, aError);
9033}
9034
9035// protected methods
9036/////////////////////////////////////////////////////////////////////////////
9037
9038/**
9039 * Helper method to finalize saving the state.
9040 *
9041 * @note Must be called from under this object's lock.
9042 *
9043 * @param aSuccess TRUE if the snapshot has been taken successfully
9044 *
9045 * @note Locks mParent + this objects for writing.
9046 */
9047HRESULT SessionMachine::endSavingState (BOOL aSuccess)
9048{
9049 LogFlowThisFuncEnter();
9050
9051 AutoCaller autoCaller (this);
9052 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
9053
9054 /* mParent->removeProgress() and saveSettings() need mParent lock */
9055 AutoMultiWriteLock2 alock (mParent, this);
9056
9057 HRESULT rc = S_OK;
9058
9059 if (aSuccess)
9060 {
9061 mSSData->mStateFilePath = mSnapshotData.mStateFilePath;
9062
9063 /* save all VM settings */
9064 rc = saveSettings();
9065 }
9066 else
9067 {
9068 /* delete the saved state file (it might have been already created) */
9069 RTFileDelete (Utf8Str (mSnapshotData.mStateFilePath));
9070 }
9071
9072 /* remove the completed progress object */
9073 mParent->removeProgress (mSnapshotData.mProgressId);
9074
9075 /* clear out the temporary saved state data */
9076 mSnapshotData.mLastState = MachineState_Null;
9077 mSnapshotData.mProgressId.clear();
9078 mSnapshotData.mStateFilePath.setNull();
9079
9080 LogFlowThisFuncLeave();
9081 return rc;
9082}
9083
9084/**
9085 * Helper method to finalize taking a snapshot.
9086 * Gets called only from #EndTakingSnapshot() that is expected to
9087 * be called by the VM process when it finishes *all* the tasks related to
9088 * taking a snapshot, either scucessfully or unsuccessfilly.
9089 *
9090 * @param aSuccess TRUE if the snapshot has been taken successfully
9091 *
9092 * @note Locks mParent + this objects for writing.
9093 */
9094HRESULT SessionMachine::endTakingSnapshot (BOOL aSuccess)
9095{
9096 LogFlowThisFuncEnter();
9097
9098 AutoCaller autoCaller (this);
9099 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
9100
9101 /* Progress object uninitialization needs mParent lock */
9102 AutoMultiWriteLock2 alock (mParent, this);
9103
9104 HRESULT rc = S_OK;
9105
9106 if (aSuccess)
9107 {
9108 /* the server progress must be completed on success */
9109 Assert (mSnapshotData.mServerProgress->completed());
9110
9111 mData->mCurrentSnapshot = mSnapshotData.mSnapshot;
9112 /* memorize the first snapshot if necessary */
9113 if (!mData->mFirstSnapshot)
9114 mData->mFirstSnapshot = mData->mCurrentSnapshot;
9115
9116 int opFlags = SaveSS_AddOp | SaveSS_UpdateCurrentId;
9117 if (mSnapshotData.mLastState != MachineState_Paused && !isModified())
9118 {
9119 /*
9120 * the machine was powered off or saved when taking a snapshot,
9121 * so reset the mCurrentStateModified flag
9122 */
9123 mData->mCurrentStateModified = FALSE;
9124 opFlags |= SaveSS_UpdateCurStateModified;
9125 }
9126
9127 rc = saveSnapshotSettings (mSnapshotData.mSnapshot, opFlags);
9128 }
9129
9130 if (!aSuccess || FAILED (rc))
9131 {
9132 if (mSnapshotData.mSnapshot)
9133 {
9134 /* wait for the completion of the server progress (diff VDI creation) */
9135 /// @todo (dmik) later, we will definitely want to cancel it instead
9136 // (when the cancel function is implemented)
9137 mSnapshotData.mServerProgress->WaitForCompletion (-1);
9138
9139 /*
9140 * delete all differencing VDIs created
9141 * (this will attach their parents back)
9142 */
9143 rc = deleteSnapshotDiffs (mSnapshotData.mSnapshot);
9144 /* continue cleanup on error */
9145
9146 /* delete the saved state file (it might have been already created) */
9147 if (mSnapshotData.mSnapshot->stateFilePath())
9148 RTFileDelete (Utf8Str (mSnapshotData.mSnapshot->stateFilePath()));
9149
9150 mSnapshotData.mSnapshot->uninit();
9151 }
9152 }
9153
9154 /* inform callbacks */
9155 if (aSuccess && SUCCEEDED (rc))
9156 mParent->onSnapshotTaken (mData->mUuid, mSnapshotData.mSnapshot->data().mId);
9157
9158 /* clear out the snapshot data */
9159 mSnapshotData.mLastState = MachineState_Null;
9160 mSnapshotData.mSnapshot.setNull();
9161 mSnapshotData.mServerProgress.setNull();
9162 /* uninitialize the combined progress (to remove it from the VBox collection) */
9163 if (!mSnapshotData.mCombinedProgress.isNull())
9164 {
9165 mSnapshotData.mCombinedProgress->uninit();
9166 mSnapshotData.mCombinedProgress.setNull();
9167 }
9168
9169 LogFlowThisFuncLeave();
9170 return rc;
9171}
9172
9173/**
9174 * Take snapshot task handler.
9175 * Must be called only by TakeSnapshotTask::handler()!
9176 *
9177 * The sole purpose of this task is to asynchronously create differencing VDIs
9178 * and copy the saved state file (when necessary). The VM process will wait
9179 * for this task to complete using the mSnapshotData.mServerProgress
9180 * returned to it.
9181 *
9182 * @note Locks mParent + this objects for writing.
9183 */
9184void SessionMachine::takeSnapshotHandler (TakeSnapshotTask &aTask)
9185{
9186 LogFlowThisFuncEnter();
9187
9188 AutoCaller autoCaller (this);
9189
9190 LogFlowThisFunc (("state=%d\n", autoCaller.state()));
9191 if (!autoCaller.isOk())
9192 {
9193 /*
9194 * we might have been uninitialized because the session was
9195 * accidentally closed by the client, so don't assert
9196 */
9197 LogFlowThisFuncLeave();
9198 return;
9199 }
9200
9201 /* endTakingSnapshot() needs mParent lock */
9202 AutoMultiWriteLock2 alock (mParent, this);
9203
9204 HRESULT rc = S_OK;
9205
9206 LogFlowThisFunc (("Creating differencing VDIs...\n"));
9207
9208 /* create new differencing hard disks and attach them to this machine */
9209 rc = createSnapshotDiffs (&mSnapshotData.mSnapshot->data().mId,
9210 mUserData->mSnapshotFolderFull,
9211 mSnapshotData.mServerProgress,
9212 true /* aOnline */);
9213
9214 if (SUCCEEDED (rc) && mSnapshotData.mLastState == MachineState_Saved)
9215 {
9216 Utf8Str stateFrom = mSSData->mStateFilePath;
9217 Utf8Str stateTo = mSnapshotData.mSnapshot->stateFilePath();
9218
9219 LogFlowThisFunc (("Copying the execution state from '%s' to '%s'...\n",
9220 stateFrom.raw(), stateTo.raw()));
9221
9222 mSnapshotData.mServerProgress->advanceOperation (
9223 Bstr (tr ("Copying the execution state")));
9224
9225 /*
9226 * We can safely leave the lock here:
9227 * mMachineState is MachineState_Saving here
9228 */
9229 alock.leave();
9230
9231 /* copy the state file */
9232 int vrc = RTFileCopyEx (stateFrom, stateTo, 0, progressCallback,
9233 static_cast <Progress *> (mSnapshotData.mServerProgress));
9234
9235 alock.enter();
9236
9237 if (VBOX_FAILURE (vrc))
9238 rc = setError (E_FAIL,
9239 tr ("Could not copy the state file '%ls' to '%ls' (%Vrc)"),
9240 stateFrom.raw(), stateTo.raw());
9241 }
9242
9243 /*
9244 * we have to call endTakingSnapshot() here if the snapshot was taken
9245 * offline, because the VM process will not do it in this case
9246 */
9247 if (mSnapshotData.mLastState != MachineState_Paused)
9248 {
9249 LogFlowThisFunc (("Finalizing the taken snapshot (rc=%08X)...\n", rc));
9250
9251 setMachineState (mSnapshotData.mLastState);
9252 updateMachineStateOnClient();
9253
9254 /* finalize the progress after setting the state, for consistency */
9255 mSnapshotData.mServerProgress->notifyComplete (rc);
9256
9257 endTakingSnapshot (SUCCEEDED (rc));
9258 }
9259 else
9260 {
9261 mSnapshotData.mServerProgress->notifyComplete (rc);
9262 }
9263
9264 LogFlowThisFuncLeave();
9265}
9266
9267/**
9268 * Discard snapshot task handler.
9269 * Must be called only by DiscardSnapshotTask::handler()!
9270 *
9271 * When aTask.subTask is true, the associated progress object is left
9272 * uncompleted on success. On failure, the progress is marked as completed
9273 * regardless of this parameter.
9274 *
9275 * @note Locks mParent + this + child objects for writing!
9276 */
9277void SessionMachine::discardSnapshotHandler (DiscardSnapshotTask &aTask)
9278{
9279 LogFlowThisFuncEnter();
9280
9281 AutoCaller autoCaller (this);
9282
9283 LogFlowThisFunc (("state=%d\n", autoCaller.state()));
9284 if (!autoCaller.isOk())
9285 {
9286 /*
9287 * we might have been uninitialized because the session was
9288 * accidentally closed by the client, so don't assert
9289 */
9290 aTask.progress->notifyComplete (
9291 E_FAIL, COM_IIDOF (IMachine), getComponentName(),
9292 tr ("The session has been accidentally closed"));
9293
9294 LogFlowThisFuncLeave();
9295 return;
9296 }
9297
9298 /* Progress::notifyComplete() et al., saveSettings() need mParent lock.
9299 * Also safely lock the snapshot stuff in the direction parent->child */
9300 AutoMultiWriteLock4 alock (mParent->lockHandle(), this->lockHandle(),
9301 aTask.snapshot->lockHandle(),
9302 aTask.snapshot->childrenLock());
9303
9304 ComObjPtr <SnapshotMachine> sm = aTask.snapshot->data().mMachine;
9305 /* no need to lock the snapshot machine since it is const by definiton */
9306
9307 HRESULT rc = S_OK;
9308
9309 /* save the snapshot ID (for callbacks) */
9310 Guid snapshotId = aTask.snapshot->data().mId;
9311
9312 do
9313 {
9314 /* first pass: */
9315 LogFlowThisFunc (("Check hard disk accessibility and affected machines...\n"));
9316
9317 HDData::HDAttachmentList::const_iterator it;
9318 for (it = sm->mHDData->mHDAttachments.begin();
9319 it != sm->mHDData->mHDAttachments.end();
9320 ++ it)
9321 {
9322 ComObjPtr <HardDiskAttachment> hda = *it;
9323 ComObjPtr <HardDisk> hd = hda->hardDisk();
9324 ComObjPtr <HardDisk> parent = hd->parent();
9325
9326 AutoWriteLock hdLock (hd);
9327
9328 if (hd->hasForeignChildren())
9329 {
9330 rc = setError (E_FAIL,
9331 tr ("One or more hard disks belonging to other machines are "
9332 "based on the hard disk '%ls' stored in the snapshot '%ls'"),
9333 hd->toString().raw(), aTask.snapshot->data().mName.raw());
9334 break;
9335 }
9336
9337 if (hd->type() == HardDiskType_Normal)
9338 {
9339 AutoWriteLock hdChildrenLock (hd->childrenLock ());
9340 size_t childrenCount = hd->children().size();
9341 if (childrenCount > 1)
9342 {
9343 rc = setError (E_FAIL,
9344 tr ("Normal hard disk '%ls' stored in the snapshot '%ls' "
9345 "has more than one child hard disk (%d)"),
9346 hd->toString().raw(), aTask.snapshot->data().mName.raw(),
9347 childrenCount);
9348 break;
9349 }
9350 }
9351 else
9352 {
9353 ComAssertMsgFailedBreak (("Invalid hard disk type %d\n", hd->type()),
9354 rc = E_FAIL);
9355 }
9356
9357 Bstr accessError;
9358 rc = hd->getAccessibleWithChildren (accessError);
9359 CheckComRCBreakRC (rc);
9360
9361 if (!accessError.isNull())
9362 {
9363 rc = setError (E_FAIL,
9364 tr ("Hard disk '%ls' stored in the snapshot '%ls' is not "
9365 "accessible (%ls)"),
9366 hd->toString().raw(), aTask.snapshot->data().mName.raw(),
9367 accessError.raw());
9368 break;
9369 }
9370
9371 rc = hd->setBusyWithChildren();
9372 if (FAILED (rc))
9373 {
9374 /* reset the busy flag of all previous hard disks */
9375 while (it != sm->mHDData->mHDAttachments.begin())
9376 (*(-- it))->hardDisk()->clearBusyWithChildren();
9377 break;
9378 }
9379 }
9380
9381 CheckComRCBreakRC (rc);
9382
9383 /* second pass: */
9384 LogFlowThisFunc (("Performing actual vdi merging...\n"));
9385
9386 for (it = sm->mHDData->mHDAttachments.begin();
9387 it != sm->mHDData->mHDAttachments.end();
9388 ++ it)
9389 {
9390 ComObjPtr <HardDiskAttachment> hda = *it;
9391 ComObjPtr <HardDisk> hd = hda->hardDisk();
9392 ComObjPtr <HardDisk> parent = hd->parent();
9393
9394 AutoWriteLock hdLock (hd);
9395
9396 Bstr hdRootString = hd->root()->toString (true /* aShort */);
9397
9398 if (parent)
9399 {
9400 if (hd->isParentImmutable())
9401 {
9402 aTask.progress->advanceOperation (Bstr (Utf8StrFmt (
9403 tr ("Discarding changes to immutable hard disk '%ls'"),
9404 hdRootString.raw())));
9405
9406 /* clear the busy flag before unregistering */
9407 hd->clearBusy();
9408
9409 /*
9410 * unregisterDiffHardDisk() is supposed to delete and uninit
9411 * the differencing hard disk
9412 */
9413 rc = mParent->unregisterDiffHardDisk (hd);
9414 CheckComRCBreakRC (rc);
9415 continue;
9416 }
9417 else
9418 {
9419 /*
9420 * differencing VDI:
9421 * merge this image to all its children
9422 */
9423
9424 aTask.progress->advanceOperation (Bstr (Utf8StrFmt (
9425 tr ("Merging changes to normal hard disk '%ls' to children"),
9426 hdRootString.raw())));
9427
9428 alock.leave();
9429
9430 rc = hd->asVDI()->mergeImageToChildren (aTask.progress);
9431
9432 alock.enter();
9433
9434 // debug code
9435 // if (it != sm->mHDData->mHDAttachments.begin())
9436 // {
9437 // rc = setError (E_FAIL, "Simulated failure");
9438 // break;
9439 //}
9440
9441 if (SUCCEEDED (rc))
9442 rc = mParent->unregisterDiffHardDisk (hd);
9443 else
9444 hd->clearBusyWithChildren();
9445
9446 CheckComRCBreakRC (rc);
9447 }
9448 }
9449 else if (hd->type() == HardDiskType_Normal)
9450 {
9451 /*
9452 * normal vdi has the only child or none
9453 * (checked in the first pass)
9454 */
9455
9456 ComObjPtr <HardDisk> child;
9457 {
9458 AutoWriteLock hdChildrenLock (hd->childrenLock ());
9459 if (hd->children().size())
9460 child = hd->children().front();
9461 }
9462
9463 if (child.isNull())
9464 {
9465 aTask.progress->advanceOperation (Bstr (Utf8StrFmt (
9466 tr ("Detaching normal hard disk '%ls'"),
9467 hdRootString.raw())));
9468
9469 /* just deassociate the normal image from this machine */
9470 hd->setMachineId (Guid());
9471 hd->setSnapshotId (Guid());
9472
9473 /* clear the busy flag */
9474 hd->clearBusy();
9475 }
9476 else
9477 {
9478 AutoWriteLock childLock (child);
9479
9480 aTask.progress->advanceOperation (Bstr (Utf8StrFmt (
9481 tr ("Preserving changes to normal hard disk '%ls'"),
9482 hdRootString.raw())));
9483
9484 ComObjPtr <Machine> cm;
9485 ComObjPtr <Snapshot> cs;
9486 ComObjPtr <HardDiskAttachment> childHda;
9487 rc = findHardDiskAttachment (child, &cm, &cs, &childHda);
9488 CheckComRCBreakRC (rc);
9489 /* must be the same machine (checked in the first pass) */
9490 ComAssertBreak (cm->mData->mUuid == mData->mUuid, rc = E_FAIL);
9491
9492 /* merge the child to this basic image */
9493
9494 alock.leave();
9495
9496 rc = child->asVDI()->mergeImageToParent (aTask.progress);
9497
9498 alock.enter();
9499
9500 if (SUCCEEDED (rc))
9501 rc = mParent->unregisterDiffHardDisk (child);
9502 else
9503 hd->clearBusyWithChildren();
9504
9505 CheckComRCBreakRC (rc);
9506
9507 /* reset the snapshot Id */
9508 hd->setSnapshotId (Guid());
9509
9510 /* replace the child image in the appropriate place */
9511 childHda->updateHardDisk (hd, FALSE /* aDirty */);
9512
9513 if (!cs)
9514 {
9515 aTask.settingsChanged = true;
9516 }
9517 else
9518 {
9519 rc = cm->saveSnapshotSettings (cs, SaveSS_UpdateAllOp);
9520 CheckComRCBreakRC (rc);
9521 }
9522 }
9523 }
9524 else
9525 {
9526 ComAssertMsgFailedBreak (("Invalid hard disk type %d\n", hd->type()),
9527 rc = E_FAIL);
9528 }
9529 }
9530
9531 /* preserve existing error info */
9532 ErrorInfoKeeper mergeEik;
9533 HRESULT mergeRc = rc;
9534
9535 if (FAILED (rc))
9536 {
9537 /* clear the busy flag on the rest of hard disks */
9538 for (++ it; it != sm->mHDData->mHDAttachments.end(); ++ it)
9539 (*it)->hardDisk()->clearBusyWithChildren();
9540 }
9541
9542 /*
9543 * we have to try to discard the snapshot even if merging failed
9544 * because some images might have been already merged (and deleted)
9545 */
9546
9547 do
9548 {
9549 LogFlowThisFunc (("Discarding the snapshot (reparenting children)...\n"));
9550
9551 /* It is important to uninitialize and delete all snapshot's hard
9552 * disk attachments as they are no longer valid -- otherwise the
9553 * code in Machine::uninitDataAndChildObjects() will mistakenly
9554 * perform hard disk deassociation. */
9555 for (HDData::HDAttachmentList::iterator it = sm->mHDData->mHDAttachments.begin();
9556 it != sm->mHDData->mHDAttachments.end();)
9557 {
9558 (*it)->uninit();
9559 it = sm->mHDData->mHDAttachments.erase (it);
9560 }
9561
9562 ComObjPtr <Snapshot> parentSnapshot = aTask.snapshot->parent();
9563
9564 /// @todo (dmik):
9565 // when we introduce clones later, discarding the snapshot
9566 // will affect the current and first snapshots of clones, if they are
9567 // direct children of this snapshot. So we will need to lock machines
9568 // associated with child snapshots as well and update mCurrentSnapshot
9569 // and/or mFirstSnapshot fields.
9570
9571 if (aTask.snapshot == mData->mCurrentSnapshot)
9572 {
9573 /* currently, the parent snapshot must refer to the same machine */
9574 ComAssertBreak (
9575 !parentSnapshot ||
9576 parentSnapshot->data().mMachine->mData->mUuid == mData->mUuid,
9577 rc = E_FAIL);
9578 mData->mCurrentSnapshot = parentSnapshot;
9579 /* mark the current state as modified */
9580 mData->mCurrentStateModified = TRUE;
9581 }
9582
9583 if (aTask.snapshot == mData->mFirstSnapshot)
9584 {
9585 /*
9586 * the first snapshot must have only one child when discarded,
9587 * or no children at all
9588 */
9589 ComAssertBreak (aTask.snapshot->children().size() <= 1, rc = E_FAIL);
9590
9591 if (aTask.snapshot->children().size() == 1)
9592 {
9593 ComObjPtr <Snapshot> childSnapshot = aTask.snapshot->children().front();
9594 ComAssertBreak (
9595 childSnapshot->data().mMachine->mData->mUuid == mData->mUuid,
9596 rc = E_FAIL);
9597 mData->mFirstSnapshot = childSnapshot;
9598 }
9599 else
9600 mData->mFirstSnapshot.setNull();
9601 }
9602
9603 /// @todo (dmik)
9604 // if we implement some warning mechanism later, we'll have
9605 // to return a warning if the state file path cannot be deleted
9606 Bstr stateFilePath = aTask.snapshot->stateFilePath();
9607 if (stateFilePath)
9608 RTFileDelete (Utf8Str (stateFilePath));
9609
9610 aTask.snapshot->discard();
9611
9612 rc = saveSnapshotSettings (parentSnapshot,
9613 SaveSS_UpdateAllOp | SaveSS_UpdateCurrentId);
9614 }
9615 while (0);
9616
9617 /* restore the merge error if any (ErrorInfo will be restored
9618 * automatically) */
9619 if (FAILED (mergeRc))
9620 rc = mergeRc;
9621 }
9622 while (0);
9623
9624 if (!aTask.subTask || FAILED (rc))
9625 {
9626 if (!aTask.subTask)
9627 {
9628 /* preserve existing error info */
9629 ErrorInfoKeeper eik;
9630
9631 /* restore the machine state */
9632 setMachineState (aTask.state);
9633 updateMachineStateOnClient();
9634
9635 /*
9636 * save settings anyway, since we've already changed the current
9637 * machine configuration
9638 */
9639 if (aTask.settingsChanged)
9640 {
9641 saveSettings (true /* aMarkCurStateAsModified */,
9642 true /* aInformCallbacksAnyway */);
9643 }
9644 }
9645
9646 /* set the result (this will try to fetch current error info on failure) */
9647 aTask.progress->notifyComplete (rc);
9648 }
9649
9650 if (SUCCEEDED (rc))
9651 mParent->onSnapshotDiscarded (mData->mUuid, snapshotId);
9652
9653 LogFlowThisFunc (("Done discarding snapshot (rc=%08X)\n", rc));
9654 LogFlowThisFuncLeave();
9655}
9656
9657/**
9658 * Discard current state task handler.
9659 * Must be called only by DiscardCurrentStateTask::handler()!
9660 *
9661 * @note Locks mParent + this object for writing.
9662 */
9663void SessionMachine::discardCurrentStateHandler (DiscardCurrentStateTask &aTask)
9664{
9665 LogFlowThisFuncEnter();
9666
9667 AutoCaller autoCaller (this);
9668
9669 LogFlowThisFunc (("state=%d\n", autoCaller.state()));
9670 if (!autoCaller.isOk())
9671 {
9672 /*
9673 * we might have been uninitialized because the session was
9674 * accidentally closed by the client, so don't assert
9675 */
9676 aTask.progress->notifyComplete (
9677 E_FAIL, COM_IIDOF (IMachine), getComponentName(),
9678 tr ("The session has been accidentally closed"));
9679
9680 LogFlowThisFuncLeave();
9681 return;
9682 }
9683
9684 /* Progress::notifyComplete() et al., saveSettings() need mParent lock */
9685 AutoMultiWriteLock2 alock (mParent, this);
9686
9687 /*
9688 * discard all current changes to mUserData (name, OSType etc.)
9689 * (note that the machine is powered off, so there is no need
9690 * to inform the direct session)
9691 */
9692 if (isModified())
9693 rollback (false /* aNotify */);
9694
9695 HRESULT rc = S_OK;
9696
9697 bool errorInSubtask = false;
9698 bool stateRestored = false;
9699
9700 const bool isLastSnapshot = mData->mCurrentSnapshot->parent().isNull();
9701
9702 do
9703 {
9704 /*
9705 * discard the saved state file if the machine was Saved prior
9706 * to this operation
9707 */
9708 if (aTask.state == MachineState_Saved)
9709 {
9710 Assert (!mSSData->mStateFilePath.isEmpty());
9711 RTFileDelete (Utf8Str (mSSData->mStateFilePath));
9712 mSSData->mStateFilePath.setNull();
9713 aTask.modifyLastState (MachineState_PoweredOff);
9714 rc = saveStateSettings (SaveSTS_StateFilePath);
9715 CheckComRCBreakRC (rc);
9716 }
9717
9718 if (aTask.discardCurrentSnapshot && !isLastSnapshot)
9719 {
9720 /*
9721 * the "discard current snapshot and state" task is in action,
9722 * the current snapshot is not the last one.
9723 * Discard the current snapshot first.
9724 */
9725
9726 DiscardSnapshotTask subTask (aTask, mData->mCurrentSnapshot);
9727 subTask.subTask = true;
9728 discardSnapshotHandler (subTask);
9729 aTask.settingsChanged = subTask.settingsChanged;
9730 if (aTask.progress->completed())
9731 {
9732 /*
9733 * the progress can be completed by a subtask only if there was
9734 * a failure
9735 */
9736 Assert (FAILED (aTask.progress->resultCode()));
9737 errorInSubtask = true;
9738 rc = aTask.progress->resultCode();
9739 break;
9740 }
9741 }
9742
9743 RTTIMESPEC snapshotTimeStamp;
9744 RTTimeSpecSetMilli (&snapshotTimeStamp, 0);
9745
9746 {
9747 ComObjPtr <Snapshot> curSnapshot = mData->mCurrentSnapshot;
9748 AutoWriteLock snapshotLock (curSnapshot);
9749
9750 /* remember the timestamp of the snapshot we're restoring from */
9751 snapshotTimeStamp = curSnapshot->data().mTimeStamp;
9752
9753 /* copy all hardware data from the current snapshot */
9754 copyFrom (curSnapshot->data().mMachine);
9755
9756 LogFlowThisFunc (("Restoring VDIs from the snapshot...\n"));
9757
9758 /* restore the attachmends from the snapshot */
9759 mHDData.backup();
9760 mHDData->mHDAttachments =
9761 curSnapshot->data().mMachine->mHDData->mHDAttachments;
9762
9763 snapshotLock.leave();
9764 alock.leave();
9765 rc = createSnapshotDiffs (NULL, mUserData->mSnapshotFolderFull,
9766 aTask.progress,
9767 false /* aOnline */);
9768 alock.enter();
9769 snapshotLock.enter();
9770
9771 if (FAILED (rc))
9772 {
9773 /* here we can still safely rollback, so do it */
9774 /* preserve existing error info */
9775 ErrorInfoKeeper eik;
9776 /* undo all changes */
9777 rollback (false /* aNotify */);
9778 break;
9779 }
9780
9781 /*
9782 * note: old VDIs will be deassociated/deleted on #commit() called
9783 * either from #saveSettings() or directly at the end
9784 */
9785
9786 /* should not have a saved state file associated at this point */
9787 Assert (mSSData->mStateFilePath.isNull());
9788
9789 if (curSnapshot->stateFilePath())
9790 {
9791 Utf8Str snapStateFilePath = curSnapshot->stateFilePath();
9792
9793 Utf8Str stateFilePath = Utf8StrFmt ("%ls%c{%Vuuid}.sav",
9794 mUserData->mSnapshotFolderFull.raw(),
9795 RTPATH_DELIMITER, mData->mUuid.raw());
9796
9797 LogFlowThisFunc (("Copying saved state file from '%s' to '%s'...\n",
9798 snapStateFilePath.raw(), stateFilePath.raw()));
9799
9800 aTask.progress->advanceOperation (
9801 Bstr (tr ("Restoring the execution state")));
9802
9803 /* copy the state file */
9804 snapshotLock.leave();
9805 alock.leave();
9806 int vrc = RTFileCopyEx (snapStateFilePath, stateFilePath,
9807 0, progressCallback, aTask.progress);
9808 alock.enter();
9809 snapshotLock.enter();
9810
9811 if (VBOX_SUCCESS (vrc))
9812 {
9813 mSSData->mStateFilePath = stateFilePath;
9814 }
9815 else
9816 {
9817 rc = setError (E_FAIL,
9818 tr ("Could not copy the state file '%s' to '%s' (%Vrc)"),
9819 snapStateFilePath.raw(), stateFilePath.raw(), vrc);
9820 break;
9821 }
9822 }
9823 }
9824
9825 bool informCallbacks = false;
9826
9827 if (aTask.discardCurrentSnapshot && isLastSnapshot)
9828 {
9829 /*
9830 * discard the current snapshot and state task is in action,
9831 * the current snapshot is the last one.
9832 * Discard the current snapshot after discarding the current state.
9833 */
9834
9835 /* commit changes to fixup hard disks before discarding */
9836 rc = commit();
9837 if (SUCCEEDED (rc))
9838 {
9839 DiscardSnapshotTask subTask (aTask, mData->mCurrentSnapshot);
9840 subTask.subTask = true;
9841 discardSnapshotHandler (subTask);
9842 aTask.settingsChanged = subTask.settingsChanged;
9843 if (aTask.progress->completed())
9844 {
9845 /*
9846 * the progress can be completed by a subtask only if there
9847 * was a failure
9848 */
9849 Assert (FAILED (aTask.progress->resultCode()));
9850 errorInSubtask = true;
9851 rc = aTask.progress->resultCode();
9852 }
9853 }
9854
9855 /*
9856 * we've committed already, so inform callbacks anyway to ensure
9857 * they don't miss some change
9858 */
9859 informCallbacks = true;
9860 }
9861
9862 /*
9863 * we have already discarded the current state, so set the
9864 * execution state accordingly no matter of the discard snapshot result
9865 */
9866 if (mSSData->mStateFilePath)
9867 setMachineState (MachineState_Saved);
9868 else
9869 setMachineState (MachineState_PoweredOff);
9870
9871 updateMachineStateOnClient();
9872 stateRestored = true;
9873
9874 if (errorInSubtask)
9875 break;
9876
9877 /* assign the timestamp from the snapshot */
9878 Assert (RTTimeSpecGetMilli (&snapshotTimeStamp) != 0);
9879 mData->mLastStateChange = snapshotTimeStamp;
9880
9881 /* mark the current state as not modified */
9882 mData->mCurrentStateModified = FALSE;
9883
9884 /* save all settings and commit */
9885 rc = saveSettings (false /* aMarkCurStateAsModified */,
9886 informCallbacks);
9887 aTask.settingsChanged = false;
9888 }
9889 while (0);
9890
9891 if (FAILED (rc))
9892 {
9893 /* preserve existing error info */
9894 ErrorInfoKeeper eik;
9895
9896 if (!stateRestored)
9897 {
9898 /* restore the machine state */
9899 setMachineState (aTask.state);
9900 updateMachineStateOnClient();
9901 }
9902
9903 /*
9904 * save all settings and commit if still modified (there is no way to
9905 * rollback properly). Note that isModified() will return true after
9906 * copyFrom(). Also save the settings if requested by the subtask.
9907 */
9908 if (isModified() || aTask.settingsChanged)
9909 {
9910 if (aTask.settingsChanged)
9911 saveSettings (true /* aMarkCurStateAsModified */,
9912 true /* aInformCallbacksAnyway */);
9913 else
9914 saveSettings();
9915 }
9916 }
9917
9918 if (!errorInSubtask)
9919 {
9920 /* set the result (this will try to fetch current error info on failure) */
9921 aTask.progress->notifyComplete (rc);
9922 }
9923
9924 if (SUCCEEDED (rc))
9925 mParent->onSnapshotDiscarded (mData->mUuid, Guid());
9926
9927 LogFlowThisFunc (("Done discarding current state (rc=%08X)\n", rc));
9928
9929 LogFlowThisFuncLeave();
9930}
9931
9932/**
9933 * Helper to change the machine state (reimplementation).
9934 *
9935 * @note Locks this object for writing.
9936 */
9937HRESULT SessionMachine::setMachineState (MachineState_T aMachineState)
9938{
9939 LogFlowThisFuncEnter();
9940 LogFlowThisFunc (("aMachineState=%d\n", aMachineState));
9941
9942 AutoCaller autoCaller (this);
9943 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
9944
9945 AutoWriteLock alock (this);
9946
9947 MachineState_T oldMachineState = mData->mMachineState;
9948
9949 AssertMsgReturn (oldMachineState != aMachineState,
9950 ("oldMachineState=%d, aMachineState=%d\n",
9951 oldMachineState, aMachineState), E_FAIL);
9952
9953 HRESULT rc = S_OK;
9954
9955 int stsFlags = 0;
9956 bool deleteSavedState = false;
9957
9958 /* detect some state transitions */
9959
9960 if (oldMachineState < MachineState_Running &&
9961 aMachineState >= MachineState_Running &&
9962 aMachineState != MachineState_Discarding)
9963 {
9964 /*
9965 * the EMT thread is about to start, so mark attached HDDs as busy
9966 * and all its ancestors as being in use
9967 */
9968 for (HDData::HDAttachmentList::const_iterator it =
9969 mHDData->mHDAttachments.begin();
9970 it != mHDData->mHDAttachments.end();
9971 ++ it)
9972 {
9973 ComObjPtr <HardDisk> hd = (*it)->hardDisk();
9974 AutoWriteLock hdLock (hd);
9975 hd->setBusy();
9976 hd->addReaderOnAncestors();
9977 }
9978 }
9979 else
9980 if (oldMachineState >= MachineState_Running &&
9981 oldMachineState != MachineState_Discarding &&
9982 aMachineState < MachineState_Running)
9983 {
9984 /*
9985 * the EMT thread stopped, so mark attached HDDs as no more busy
9986 * and remove the in-use flag from all its ancestors
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->releaseReaderOnAncestors();
9996 hd->clearBusy();
9997 }
9998 }
9999
10000 if (oldMachineState == MachineState_Restoring)
10001 {
10002 if (aMachineState != MachineState_Saved)
10003 {
10004 /*
10005 * delete the saved state file once the machine has finished
10006 * restoring from it (note that Console sets the state from
10007 * Restoring to Saved if the VM couldn't restore successfully,
10008 * to give the user an ability to fix an error and retry --
10009 * we keep the saved state file in this case)
10010 */
10011 deleteSavedState = true;
10012 }
10013 }
10014 else
10015 if (oldMachineState == MachineState_Saved &&
10016 (aMachineState == MachineState_PoweredOff ||
10017 aMachineState == MachineState_Aborted))
10018 {
10019 /*
10020 * delete the saved state after Console::DiscardSavedState() is called
10021 * or if the VM process (owning a direct VM session) crashed while the
10022 * VM was Saved
10023 */
10024
10025 /// @todo (dmik)
10026 // Not sure that deleting the saved state file just because of the
10027 // client death before it attempted to restore the VM is a good
10028 // thing. But when it crashes we need to go to the Aborted state
10029 // which cannot have the saved state file associated... The only
10030 // way to fix this is to make the Aborted condition not a VM state
10031 // but a bool flag: i.e., when a crash occurs, set it to true and
10032 // change the state to PoweredOff or Saved depending on the
10033 // saved state presence.
10034
10035 deleteSavedState = true;
10036 mData->mCurrentStateModified = TRUE;
10037 stsFlags |= SaveSTS_CurStateModified;
10038 }
10039
10040 if (aMachineState == MachineState_Starting ||
10041 aMachineState == MachineState_Restoring)
10042 {
10043 /*
10044 * set the current state modified flag to indicate that the
10045 * current state is no more identical to the state in the
10046 * current snapshot
10047 */
10048 if (!mData->mCurrentSnapshot.isNull())
10049 {
10050 mData->mCurrentStateModified = TRUE;
10051 stsFlags |= SaveSTS_CurStateModified;
10052 }
10053 }
10054
10055 if (deleteSavedState == true)
10056 {
10057 Assert (!mSSData->mStateFilePath.isEmpty());
10058 RTFileDelete (Utf8Str (mSSData->mStateFilePath));
10059 mSSData->mStateFilePath.setNull();
10060 stsFlags |= SaveSTS_StateFilePath;
10061 }
10062
10063 /* redirect to the underlying peer machine */
10064 mPeer->setMachineState (aMachineState);
10065
10066 if (aMachineState == MachineState_PoweredOff ||
10067 aMachineState == MachineState_Aborted ||
10068 aMachineState == MachineState_Saved)
10069 {
10070 /* the machine has stopped execution
10071 * (or the saved state file was adopted) */
10072 stsFlags |= SaveSTS_StateTimeStamp;
10073 }
10074
10075 if ((oldMachineState == MachineState_PoweredOff ||
10076 oldMachineState == MachineState_Aborted) &&
10077 aMachineState == MachineState_Saved)
10078 {
10079 /* the saved state file was adopted */
10080 Assert (!mSSData->mStateFilePath.isNull());
10081 stsFlags |= SaveSTS_StateFilePath;
10082 }
10083
10084 rc = saveStateSettings (stsFlags);
10085
10086 if ((oldMachineState != MachineState_PoweredOff &&
10087 oldMachineState != MachineState_Aborted) &&
10088 (aMachineState == MachineState_PoweredOff ||
10089 aMachineState == MachineState_Aborted))
10090 {
10091 /*
10092 * clear differencing hard disks based on immutable hard disks
10093 * once we've been shut down for any reason
10094 */
10095 rc = wipeOutImmutableDiffs();
10096 }
10097
10098 LogFlowThisFunc (("rc=%08X\n", rc));
10099 LogFlowThisFuncLeave();
10100 return rc;
10101}
10102
10103/**
10104 * Sends the current machine state value to the VM process.
10105 *
10106 * @note Locks this object for reading, then calls a client process.
10107 */
10108HRESULT SessionMachine::updateMachineStateOnClient()
10109{
10110 AutoCaller autoCaller (this);
10111 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
10112
10113 ComPtr <IInternalSessionControl> directControl;
10114 {
10115 AutoReadLock alock (this);
10116 AssertReturn (!!mData, E_FAIL);
10117 directControl = mData->mSession.mDirectControl;
10118
10119 /* directControl may be already set to NULL here in #OnSessionEnd()
10120 * called too early by the direct session process while there is still
10121 * some operation (like discarding the snapshot) in progress. The client
10122 * process in this case is waiting inside Session::close() for the
10123 * "end session" process object to complete, while #uninit() called by
10124 * #checkForDeath() on the Watcher thread is waiting for the pending
10125 * operation to complete. For now, we accept this inconsitent behavior
10126 * and simply do nothing here. */
10127
10128 if (mData->mSession.mState == SessionState_Closing)
10129 return S_OK;
10130
10131 AssertReturn (!directControl.isNull(), E_FAIL);
10132 }
10133
10134 return directControl->UpdateMachineState (mData->mMachineState);
10135}
10136
10137/* static */
10138DECLCALLBACK(int) SessionMachine::taskHandler (RTTHREAD thread, void *pvUser)
10139{
10140 AssertReturn (pvUser, VERR_INVALID_POINTER);
10141
10142 Task *task = static_cast <Task *> (pvUser);
10143 task->handler();
10144
10145 // it's our responsibility to delete the task
10146 delete task;
10147
10148 return 0;
10149}
10150
10151/////////////////////////////////////////////////////////////////////////////
10152// SnapshotMachine class
10153/////////////////////////////////////////////////////////////////////////////
10154
10155DEFINE_EMPTY_CTOR_DTOR (SnapshotMachine)
10156
10157HRESULT SnapshotMachine::FinalConstruct()
10158{
10159 LogFlowThisFunc (("\n"));
10160
10161 /* set the proper type to indicate we're the SnapshotMachine instance */
10162 unconst (mType) = IsSnapshotMachine;
10163
10164 return S_OK;
10165}
10166
10167void SnapshotMachine::FinalRelease()
10168{
10169 LogFlowThisFunc (("\n"));
10170
10171 uninit();
10172}
10173
10174/**
10175 * Initializes the SnapshotMachine object when taking a snapshot.
10176 *
10177 * @param aSessionMachine machine to take a snapshot from
10178 * @param aSnapshotId snapshot ID of this snapshot machine
10179 * @param aStateFilePath file where the execution state will be later saved
10180 * (or NULL for the offline snapshot)
10181 *
10182 * @note The aSessionMachine must be locked for writing.
10183 */
10184HRESULT SnapshotMachine::init (SessionMachine *aSessionMachine,
10185 INPTR GUIDPARAM aSnapshotId,
10186 INPTR BSTR aStateFilePath)
10187{
10188 LogFlowThisFuncEnter();
10189 LogFlowThisFunc (("mName={%ls}\n", aSessionMachine->mUserData->mName.raw()));
10190
10191 AssertReturn (aSessionMachine && !Guid (aSnapshotId).isEmpty(), E_INVALIDARG);
10192
10193 /* Enclose the state transition NotReady->InInit->Ready */
10194 AutoInitSpan autoInitSpan (this);
10195 AssertReturn (autoInitSpan.isOk(), E_UNEXPECTED);
10196
10197 AssertReturn (aSessionMachine->isWriteLockOnCurrentThread(), E_FAIL);
10198
10199 mSnapshotId = aSnapshotId;
10200
10201 /* memorize the primary Machine instance (i.e. not SessionMachine!) */
10202 unconst (mPeer) = aSessionMachine->mPeer;
10203 /* share the parent pointer */
10204 unconst (mParent) = mPeer->mParent;
10205
10206 /* take the pointer to Data to share */
10207 mData.share (mPeer->mData);
10208 /*
10209 * take the pointer to UserData to share
10210 * (our UserData must always be the same as Machine's data)
10211 */
10212 mUserData.share (mPeer->mUserData);
10213 /* make a private copy of all other data (recent changes from SessionMachine) */
10214 mHWData.attachCopy (aSessionMachine->mHWData);
10215 mHDData.attachCopy (aSessionMachine->mHDData);
10216
10217 /* SSData is always unique for SnapshotMachine */
10218 mSSData.allocate();
10219 mSSData->mStateFilePath = aStateFilePath;
10220
10221 /*
10222 * create copies of all shared folders (mHWData after attiching a copy
10223 * contains just references to original objects)
10224 */
10225 for (HWData::SharedFolderList::iterator it = mHWData->mSharedFolders.begin();
10226 it != mHWData->mSharedFolders.end();
10227 ++ it)
10228 {
10229 ComObjPtr <SharedFolder> folder;
10230 folder.createObject();
10231 HRESULT rc = folder->initCopy (this, *it);
10232 CheckComRCReturnRC (rc);
10233 *it = folder;
10234 }
10235
10236 /* create all other child objects that will be immutable private copies */
10237
10238 unconst (mBIOSSettings).createObject();
10239 mBIOSSettings->initCopy (this, mPeer->mBIOSSettings);
10240
10241#ifdef VBOX_VRDP
10242 unconst (mVRDPServer).createObject();
10243 mVRDPServer->initCopy (this, mPeer->mVRDPServer);
10244#endif
10245
10246 unconst (mDVDDrive).createObject();
10247 mDVDDrive->initCopy (this, mPeer->mDVDDrive);
10248
10249 unconst (mFloppyDrive).createObject();
10250 mFloppyDrive->initCopy (this, mPeer->mFloppyDrive);
10251
10252 unconst (mAudioAdapter).createObject();
10253 mAudioAdapter->initCopy (this, mPeer->mAudioAdapter);
10254
10255 unconst (mUSBController).createObject();
10256 mUSBController->initCopy (this, mPeer->mUSBController);
10257
10258 unconst (mSATAController).createObject();
10259 mSATAController->initCopy (this, mPeer->mSATAController);
10260
10261 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
10262 {
10263 unconst (mNetworkAdapters [slot]).createObject();
10264 mNetworkAdapters [slot]->initCopy (this, mPeer->mNetworkAdapters [slot]);
10265 }
10266
10267 for (ULONG slot = 0; slot < ELEMENTS (mSerialPorts); slot ++)
10268 {
10269 unconst (mSerialPorts [slot]).createObject();
10270 mSerialPorts [slot]->initCopy (this, mPeer->mSerialPorts [slot]);
10271 }
10272
10273 for (ULONG slot = 0; slot < ELEMENTS (mParallelPorts); slot ++)
10274 {
10275 unconst (mParallelPorts [slot]).createObject();
10276 mParallelPorts [slot]->initCopy (this, mPeer->mParallelPorts [slot]);
10277 }
10278
10279 /* Confirm a successful initialization when it's the case */
10280 autoInitSpan.setSucceeded();
10281
10282 LogFlowThisFuncLeave();
10283 return S_OK;
10284}
10285
10286/**
10287 * Initializes the SnapshotMachine object when loading from the settings file.
10288 *
10289 * @param aMachine machine the snapshot belngs to
10290 * @param aHWNode <Hardware> node
10291 * @param aHDAsNode <HardDiskAttachments> node
10292 * @param aSnapshotId snapshot ID of this snapshot machine
10293 * @param aStateFilePath file where the execution state is saved
10294 * (or NULL for the offline snapshot)
10295 *
10296 * @note Doesn't lock anything.
10297 */
10298HRESULT SnapshotMachine::init (Machine *aMachine,
10299 const settings::Key &aHWNode,
10300 const settings::Key &aHDAsNode,
10301 INPTR GUIDPARAM aSnapshotId, INPTR BSTR aStateFilePath)
10302{
10303 LogFlowThisFuncEnter();
10304 LogFlowThisFunc (("mName={%ls}\n", aMachine->mUserData->mName.raw()));
10305
10306 AssertReturn (aMachine && !aHWNode.isNull() && !aHDAsNode.isNull() &&
10307 !Guid (aSnapshotId).isEmpty(),
10308 E_INVALIDARG);
10309
10310 /* Enclose the state transition NotReady->InInit->Ready */
10311 AutoInitSpan autoInitSpan (this);
10312 AssertReturn (autoInitSpan.isOk(), E_UNEXPECTED);
10313
10314 /* Don't need to lock aMachine when VirtualBox is starting up */
10315
10316 mSnapshotId = aSnapshotId;
10317
10318 /* memorize the primary Machine instance */
10319 unconst (mPeer) = aMachine;
10320 /* share the parent pointer */
10321 unconst (mParent) = mPeer->mParent;
10322
10323 /* take the pointer to Data to share */
10324 mData.share (mPeer->mData);
10325 /*
10326 * take the pointer to UserData to share
10327 * (our UserData must always be the same as Machine's data)
10328 */
10329 mUserData.share (mPeer->mUserData);
10330 /* allocate private copies of all other data (will be loaded from settings) */
10331 mHWData.allocate();
10332 mHDData.allocate();
10333
10334 /* SSData is always unique for SnapshotMachine */
10335 mSSData.allocate();
10336 mSSData->mStateFilePath = aStateFilePath;
10337
10338 /* create all other child objects that will be immutable private copies */
10339
10340 unconst (mBIOSSettings).createObject();
10341 mBIOSSettings->init (this);
10342
10343#ifdef VBOX_VRDP
10344 unconst (mVRDPServer).createObject();
10345 mVRDPServer->init (this);
10346#endif
10347
10348 unconst (mDVDDrive).createObject();
10349 mDVDDrive->init (this);
10350
10351 unconst (mFloppyDrive).createObject();
10352 mFloppyDrive->init (this);
10353
10354 unconst (mAudioAdapter).createObject();
10355 mAudioAdapter->init (this);
10356
10357 unconst (mUSBController).createObject();
10358 mUSBController->init (this);
10359
10360 unconst (mSATAController).createObject();
10361 mSATAController->init (this);
10362
10363 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
10364 {
10365 unconst (mNetworkAdapters [slot]).createObject();
10366 mNetworkAdapters [slot]->init (this, slot);
10367 }
10368
10369 for (ULONG slot = 0; slot < ELEMENTS (mSerialPorts); slot ++)
10370 {
10371 unconst (mSerialPorts [slot]).createObject();
10372 mSerialPorts [slot]->init (this, slot);
10373 }
10374
10375 for (ULONG slot = 0; slot < ELEMENTS (mParallelPorts); slot ++)
10376 {
10377 unconst (mParallelPorts [slot]).createObject();
10378 mParallelPorts [slot]->init (this, slot);
10379 }
10380
10381 /* load hardware and harddisk settings */
10382
10383 HRESULT rc = loadHardware (aHWNode);
10384 if (SUCCEEDED (rc))
10385 rc = loadHardDisks (aHDAsNode, true /* aRegistered */, &mSnapshotId);
10386
10387 if (SUCCEEDED (rc))
10388 {
10389 /* commit all changes made during the initialization */
10390 commit();
10391 }
10392
10393 /* Confirm a successful initialization when it's the case */
10394 if (SUCCEEDED (rc))
10395 autoInitSpan.setSucceeded();
10396
10397 LogFlowThisFuncLeave();
10398 return rc;
10399}
10400
10401/**
10402 * Uninitializes this SnapshotMachine object.
10403 */
10404void SnapshotMachine::uninit()
10405{
10406 LogFlowThisFuncEnter();
10407
10408 /* Enclose the state transition Ready->InUninit->NotReady */
10409 AutoUninitSpan autoUninitSpan (this);
10410 if (autoUninitSpan.uninitDone())
10411 return;
10412
10413 uninitDataAndChildObjects();
10414
10415 /* free the essential data structure last */
10416 mData.free();
10417
10418 unconst (mParent).setNull();
10419 unconst (mPeer).setNull();
10420
10421 LogFlowThisFuncLeave();
10422}
10423
10424// util::Lockable interface
10425////////////////////////////////////////////////////////////////////////////////
10426
10427/**
10428 * Overrides VirtualBoxBase::lockHandle() in order to share the lock handle
10429 * with the primary Machine instance (mPeer).
10430 */
10431RWLockHandle *SnapshotMachine::lockHandle() const
10432{
10433 AssertReturn (!mPeer.isNull(), NULL);
10434 return mPeer->lockHandle();
10435}
10436
10437// public methods only for internal purposes
10438////////////////////////////////////////////////////////////////////////////////
10439
10440/**
10441 * Called by the snapshot object associated with this SnapshotMachine when
10442 * snapshot data such as name or description is changed.
10443 *
10444 * @note Locks this object for writing.
10445 */
10446HRESULT SnapshotMachine::onSnapshotChange (Snapshot *aSnapshot)
10447{
10448 AutoWriteLock alock (this);
10449
10450 mPeer->saveSnapshotSettings (aSnapshot, SaveSS_UpdateAttrsOp);
10451
10452 /* inform callbacks */
10453 mParent->onSnapshotChange (mData->mUuid, aSnapshot->data().mId);
10454
10455 return S_OK;
10456}
10457
10458
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