VirtualBox

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

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

Main/Settings: Implemented support for settings file auto-conversion at VBoxSVC startup (#2705).

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