VirtualBox

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

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

Dynamic memory allocation for the path variable.

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