VirtualBox

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

Last change on this file since 6310 was 6276, checked in by vboxsync, 17 years ago

Main: start VBoxHeadless instead of VBoxVRDP

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