VirtualBox

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

Last change on this file since 10477 was 10420, checked in by vboxsync, 17 years ago

FE/Qt4: Named the Qt3 version VirtualBox3 & the Qt4 version VirtualBox.

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