VirtualBox

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

Last change on this file since 7392 was 7349, checked in by vboxsync, 17 years ago

Main: Added IVirtualBox/IMachine::saveSettingsWithBackup() for easier support of creating backup copies of the settings files when auto-converting.

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