VirtualBox

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

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

Some refinements. the inode must be different. greate.

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