VirtualBox

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

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

Renaming TriStateBool enum qualifiers:
True => TSTrue
False => TSFalse
Default => TSDefault

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