VirtualBox

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

Last change on this file since 1856 was 1721, checked in by vboxsync, 18 years ago

Implemented new 'MonitorCount' VM setting, to be used by VGA device and NT guest driver.

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