VirtualBox

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

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

gcc-4.2 warnings

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

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