VirtualBox

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

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

Main & All Frontends: replaced the IGuestOSType IMachine::OSType property with the wstring IMachine::OSTypeId property (+ converted IGuest and IGuestOSType to VirtualBoxBaseNEXT semantics).

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