VirtualBox

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

Last change on this file since 3467 was 3424, checked in by vboxsync, 17 years ago

Main: Removed outdated CHECK_MACHINE_MUTABILITY and CHECK_STATE macros.

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

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