VirtualBox

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

Last change on this file since 3411 was 3191, checked in by vboxsync, 17 years ago

Main: Made it build on OS/2.

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

© 2023 Oracle
ContactPrivacy policyTerms of Use