VirtualBox

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

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

Worked around a problem with Windows splitting process arguments by spaces

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

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