VirtualBox

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

Last change on this file since 2561 was 2540, checked in by vboxsync, 18 years ago

Main & All Frontends: Improved console window activation helper APIs (still no success on X11, works well on Win32, the old method of activation is disabled).

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

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette