VirtualBox

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

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

Added setting for nested paging (default on).

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