VirtualBox

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

Last change on this file since 7244 was 7207, checked in by vboxsync, 17 years ago

Main: Reworked enums to avoid 1) weird duplication of enum name when referring to enum values in cross-platform code; 2) possible clashes on Win32 due to putting identifiers like Paused or Disabled to the global namespace (via C enums). In the new style, enums are used like this: a) USBDeviceState_T v = USBDeviceState_Busy from cross-platform non-Qt code; b) KUSBDeviceState v = KUSBDeviceState_Busy from Qt code; c) USBDeviceState v = USBDeviceState_Busy from plain Win32 and d) PRUInt32 USBDeviceState v = USBDeviceState::Busy from plain XPCOM.

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