VirtualBox

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

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

Main & All Frontends: Prototyped a bunch of Main API changes (IVirtualBoxErrorInfo extension for cascading errors; IMachine/IConsoleCallback extension to properly activate the console window; IVirtualBoxCallback::onExtraDataCanChange() support for error messages; minor IHost::createUSBDeviceFilter/removeUSBDeviceFilter corrections).

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