VirtualBox

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

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

Main/Frontends: Next step to support asynchronous USB device flow.

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