VirtualBox

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

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

Performance API, version 0, webservice broken.

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