VirtualBox

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

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

Flush the config file before closing it to try prevent data loss on panic/fault/whatever. TODO: flush the directory too.

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