VirtualBox

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

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

default is bidirectional clipboard sync

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 317.1 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 else
4240 AssertMsgFailed (("Invalid driver: %ls\n", driver.raw()));
4241 mAudioAdapter->COMSETTER(AudioDriver) (audioDriver);
4242
4243 CFGLDRReleaseNode (audioAdapterNode);
4244 }
4245
4246 /* Shared folders (optional) */
4247 /// @todo (dmik) make required on next format change!
4248 do
4249 {
4250 CFGNODE sharedFoldersNode = 0;
4251 CFGLDRGetChildNode (aNode, "SharedFolders", 0, &sharedFoldersNode);
4252
4253 if (!sharedFoldersNode)
4254 break;
4255
4256 HRESULT rc = S_OK;
4257
4258 unsigned cFolders = 0;
4259 CFGLDRCountChildren (sharedFoldersNode, "SharedFolder", &cFolders);
4260
4261 for (unsigned i = 0; i < cFolders; i++)
4262 {
4263 CFGNODE folderNode = 0;
4264 CFGLDRGetChildNode (sharedFoldersNode, "SharedFolder", i, &folderNode);
4265 ComAssertBreak (folderNode, rc = E_FAIL);
4266
4267 // folder logical name (required)
4268 Bstr name;
4269 CFGLDRQueryBSTR (folderNode, "name", name.asOutParam());
4270
4271 // folder host path (required)
4272 Bstr hostPath;
4273 CFGLDRQueryBSTR (folderNode, "hostPath", hostPath.asOutParam());
4274
4275 rc = CreateSharedFolder (name, hostPath);
4276 if (FAILED (rc))
4277 break;
4278
4279 CFGLDRReleaseNode (folderNode);
4280 }
4281
4282 CFGLDRReleaseNode (sharedFoldersNode);
4283 if (FAILED (rc))
4284 return rc;
4285 }
4286 while (0);
4287
4288 /* Clipboard node (currently not required) */
4289 /// @todo (dmik) make required on next format change!
4290 {
4291 /* default value in case the node is not there */
4292 mHWData->mClipboardMode = ClipboardMode_ClipDisabled;
4293
4294 CFGNODE clipNode = 0;
4295 CFGLDRGetChildNode (aNode, "Clipboard", 0, &clipNode);
4296 if (clipNode)
4297 {
4298 Bstr mode;
4299 CFGLDRQueryBSTR (clipNode, "mode", mode.asOutParam());
4300 if (mode == L"Disabled")
4301 mHWData->mClipboardMode = ClipboardMode_ClipDisabled;
4302 else if (mode == L"HostToGuest")
4303 mHWData->mClipboardMode = ClipboardMode_ClipHostToGuest;
4304 else if (mode == L"GuestToHost")
4305 mHWData->mClipboardMode = ClipboardMode_ClipGuestToHost;
4306 else if (mode == L"Bidirectional")
4307 mHWData->mClipboardMode = ClipboardMode_ClipBidirectional;
4308 else
4309 AssertMsgFailed (("%ls clipboard mode is invalid\n", mode.raw()));
4310 CFGLDRReleaseNode (clipNode);
4311 }
4312 }
4313
4314 return S_OK;
4315}
4316
4317/**
4318 * @param aNode <HardDiskAttachments> node
4319 * @param aRegistered true when the machine is being loaded on VirtualBox
4320 * startup, or when a snapshot is being loaded (wchich
4321 * currently can happen on startup only)
4322 * @param aSnapshotId pointer to the snapshot ID if this is a snapshot machine
4323 */
4324HRESULT Machine::loadHardDisks (CFGNODE aNode, bool aRegistered,
4325 const Guid *aSnapshotId /* = NULL */)
4326{
4327 AssertReturn (aNode, E_INVALIDARG);
4328 AssertReturn ((mType == IsMachine && aSnapshotId == NULL) ||
4329 (mType == IsSnapshotMachine && aSnapshotId != NULL), E_FAIL);
4330
4331 HRESULT rc = S_OK;
4332
4333 unsigned cbDisks = 0;
4334 CFGLDRCountChildren (aNode, "HardDiskAttachment", &cbDisks);
4335
4336 if (!aRegistered && cbDisks > 0)
4337 {
4338 /* when the machine is being loaded (opened) from a file, it cannot
4339 * have hard disks attached (this should not happen normally,
4340 * because we don't allow to attach hard disks to an unregistered
4341 * VM at all */
4342 return setError (E_FAIL,
4343 tr ("Unregistered machine '%ls' cannot have hard disks attached "
4344 "(found %d hard disk attachments)"),
4345 mUserData->mName.raw(), cbDisks);
4346 }
4347
4348 for (unsigned i = 0; i < cbDisks && SUCCEEDED (rc); ++ i)
4349 {
4350 CFGNODE hdNode;
4351 CFGLDRGetChildNode (aNode, "HardDiskAttachment", i, &hdNode);
4352 ComAssertRet (hdNode, E_FAIL);
4353
4354 do
4355 {
4356 /* hardDisk uuid (required) */
4357 Guid uuid;
4358 CFGLDRQueryUUID (hdNode, "hardDisk", uuid.ptr());
4359 /* bus (controller) type (required) */
4360 Bstr bus;
4361 CFGLDRQueryBSTR (hdNode, "bus", bus.asOutParam());
4362 /* device (required) */
4363 Bstr device;
4364 CFGLDRQueryBSTR (hdNode, "device", device.asOutParam());
4365
4366 /* find a hard disk by UUID */
4367 ComObjPtr <HardDisk> hd;
4368 rc = mParent->getHardDisk (uuid, hd);
4369 if (FAILED (rc))
4370 break;
4371
4372 AutoLock hdLock (hd);
4373
4374 if (!hd->machineId().isEmpty())
4375 {
4376 rc = setError (E_FAIL,
4377 tr ("Hard disk '%ls' with UUID {%s} is already "
4378 "attached to a machine with UUID {%s} (see '%ls')"),
4379 hd->toString().raw(), uuid.toString().raw(),
4380 hd->machineId().toString().raw(),
4381 mData->mConfigFileFull.raw());
4382 break;
4383 }
4384
4385 if (hd->type() == HardDiskType_ImmutableHardDisk)
4386 {
4387 rc = setError (E_FAIL,
4388 tr ("Immutable hard disk '%ls' with UUID {%s} cannot be "
4389 "directly attached to a machine (see '%ls')"),
4390 hd->toString().raw(), uuid.toString().raw(),
4391 mData->mConfigFileFull.raw());
4392 break;
4393 }
4394
4395 /* attach the device */
4396 DiskControllerType_T ctl = DiskControllerType_InvalidController;
4397 LONG dev = -1;
4398
4399 if (bus == L"ide0")
4400 {
4401 ctl = DiskControllerType_IDE0Controller;
4402 if (device == L"master")
4403 dev = 0;
4404 else if (device == L"slave")
4405 dev = 1;
4406 else
4407 ComAssertMsgFailedBreak (("Invalid device: %ls\n", device.raw()),
4408 rc = E_FAIL);
4409 }
4410 else if (bus == L"ide1")
4411 {
4412 ctl = DiskControllerType_IDE1Controller;
4413 if (device == L"master")
4414 rc = setError (E_FAIL, tr("Could not attach a disk as a master "
4415 "device on the secondary controller"));
4416 else if (device == L"slave")
4417 dev = 1;
4418 else
4419 ComAssertMsgFailedBreak (("Invalid device: %ls\n", device.raw()),
4420 rc = E_FAIL);
4421 }
4422 else
4423 ComAssertMsgFailedBreak (("Invalid bus: %ls\n", bus.raw()),
4424 rc = E_FAIL);
4425
4426 ComObjPtr <HardDiskAttachment> attachment;
4427 attachment.createObject();
4428 rc = attachment->init (hd, ctl, dev, false /* aDirty */);
4429 if (FAILED (rc))
4430 break;
4431
4432 /* associate the hard disk with this machine */
4433 hd->setMachineId (mData->mUuid);
4434
4435 /* associate the hard disk with the given snapshot ID */
4436 if (mType == IsSnapshotMachine)
4437 hd->setSnapshotId (*aSnapshotId);
4438
4439 mHDData->mHDAttachments.push_back (attachment);
4440 }
4441 while (0);
4442
4443 CFGLDRReleaseNode (hdNode);
4444 }
4445
4446 return rc;
4447}
4448
4449/**
4450 * Creates a config loader and loads the settings file.
4451 *
4452 * @param aIsNew |true| if a newly created settings file is to be opened
4453 * (must be the case only when called from #saveSettings())
4454 *
4455 * @note
4456 * XML Schema errors are not detected by this method because
4457 * it assumes that it will load settings from an exclusively locked
4458 * file (using a file handle) that was previously validated when opened
4459 * for the first time. Thus, this method should be used only when
4460 * it's necessary to modify (save) the settings file.
4461 *
4462 * @note The object must be locked at least for reading before calling
4463 * this method.
4464 */
4465HRESULT Machine::openConfigLoader (CFGHANDLE *aLoader, bool aIsNew /* = false */)
4466{
4467 AssertReturn (aLoader, E_FAIL);
4468
4469 /* The settings file must be created and locked at this point */
4470 ComAssertRet (isConfigLocked(), E_FAIL);
4471
4472 /* load the config file */
4473 int vrc = CFGLDRLoad (aLoader,
4474 Utf8Str (mData->mConfigFileFull), mData->mHandleCfgFile,
4475 aIsNew ? NULL : XmlSchemaNS, true, cfgLdrEntityResolver,
4476 NULL);
4477 ComAssertRCRet (vrc, E_FAIL);
4478
4479 return S_OK;
4480}
4481
4482/**
4483 * Closes the config loader previously created by #openConfigLoader().
4484 * If \a aSaveBeforeClose is true, then the config is saved to the settings file
4485 * before closing. If saving fails, a proper error message is set.
4486 *
4487 * @param aSaveBeforeClose whether to save the config before closing or not
4488 */
4489HRESULT Machine::closeConfigLoader (CFGHANDLE aLoader, bool aSaveBeforeClose)
4490{
4491 HRESULT rc = S_OK;
4492
4493 if (aSaveBeforeClose)
4494 {
4495 char *loaderError = NULL;
4496 int vrc = CFGLDRSave (aLoader, &loaderError);
4497 if (VBOX_FAILURE (vrc))
4498 {
4499 rc = setError (E_FAIL,
4500 tr ("Could not save the settings file '%ls' (%Vrc)%s%s"),
4501 mData->mConfigFileFull.raw(), vrc,
4502 loaderError ? ".\n" : "", loaderError ? loaderError : "");
4503 if (loaderError)
4504 RTMemTmpFree (loaderError);
4505 }
4506 }
4507
4508 CFGLDRFree (aLoader);
4509
4510 return rc;
4511}
4512
4513/**
4514 * Searches for a <Snapshot> node for the given snapshot.
4515 * If the search is successful, \a aSnapshotNode will contain the found node.
4516 * In this case, \a aSnapshotsNode can be NULL meaning the found node is a
4517 * direct child of \a aMachineNode.
4518 *
4519 * If the search fails, a failure is returned and both \a aSnapshotsNode and
4520 * \a aSnapshotNode are set to 0.
4521 *
4522 * @param aSnapshot snapshot to search for
4523 * @param aMachineNode <Machine> node to start from
4524 * @param aSnapshotsNode <Snapshots> node containing the found <Snapshot> node
4525 * (may be NULL if the caller is not interested)
4526 * @param aSnapshotNode found <Snapshot> node
4527 */
4528HRESULT Machine::findSnapshotNode (Snapshot *aSnapshot, CFGNODE aMachineNode,
4529 CFGNODE *aSnapshotsNode, CFGNODE *aSnapshotNode)
4530{
4531 AssertReturn (aSnapshot && aMachineNode && aSnapshotNode, E_FAIL);
4532
4533 if (aSnapshotsNode)
4534 *aSnapshotsNode = 0;
4535 *aSnapshotNode = 0;
4536
4537 // build the full uuid path (from the fist parent to the given snapshot)
4538 std::list <Guid> path;
4539 {
4540 ComObjPtr <Snapshot> parent = aSnapshot;
4541 while (parent)
4542 {
4543 path.push_front (parent->data().mId);
4544 parent = parent->parent();
4545 }
4546 }
4547
4548 CFGNODE snapshotsNode = aMachineNode;
4549 CFGNODE snapshotNode = 0;
4550
4551 for (std::list <Guid>::const_iterator it = path.begin();
4552 it != path.end();
4553 ++ it)
4554 {
4555 if (snapshotNode)
4556 {
4557 // proceed to the nested <Snapshots> node
4558 Assert (snapshotsNode);
4559 if (snapshotsNode != aMachineNode)
4560 {
4561 CFGLDRReleaseNode (snapshotsNode);
4562 snapshotsNode = 0;
4563 }
4564 CFGLDRGetChildNode (snapshotNode, "Snapshots", 0, &snapshotsNode);
4565 CFGLDRReleaseNode (snapshotNode);
4566 snapshotNode = 0;
4567 }
4568
4569 AssertReturn (snapshotsNode, E_FAIL);
4570
4571 unsigned count = 0, i = 0;
4572 CFGLDRCountChildren (snapshotsNode, "Snapshot", &count);
4573 for (; i < count; ++ i)
4574 {
4575 snapshotNode = 0;
4576 CFGLDRGetChildNode (snapshotsNode, "Snapshot", i, &snapshotNode);
4577 Guid id;
4578 CFGLDRQueryUUID (snapshotNode, "uuid", id.ptr());
4579 if (id == (*it))
4580 {
4581 // we keep (don't release) snapshotNode and snapshotsNode
4582 break;
4583 }
4584 CFGLDRReleaseNode (snapshotNode);
4585 snapshotNode = 0;
4586 }
4587
4588 if (i == count)
4589 {
4590 // the next uuid is not found, no need to continue...
4591 AssertFailed();
4592 if (snapshotsNode != aMachineNode)
4593 {
4594 CFGLDRReleaseNode (snapshotsNode);
4595 snapshotsNode = 0;
4596 }
4597 break;
4598 }
4599 }
4600
4601 // we must always succesfully find the node
4602 AssertReturn (snapshotNode, E_FAIL);
4603 AssertReturn (snapshotsNode, E_FAIL);
4604
4605 if (aSnapshotsNode)
4606 *aSnapshotsNode = snapshotsNode != aMachineNode ? snapshotsNode : 0;
4607 *aSnapshotNode = snapshotNode;
4608
4609 return S_OK;
4610}
4611
4612/**
4613 * Returns the snapshot with the given UUID or fails of no such snapshot.
4614 *
4615 * @param aId snapshot UUID to find (empty UUID refers the first snapshot)
4616 * @param aSnapshot where to return the found snapshot
4617 * @param aSetError true to set extended error info on failure
4618 */
4619HRESULT Machine::findSnapshot (const Guid &aId, ComObjPtr <Snapshot> &aSnapshot,
4620 bool aSetError /* = false */)
4621{
4622 if (!mData->mFirstSnapshot)
4623 {
4624 if (aSetError)
4625 return setError (E_FAIL,
4626 tr ("This machine does not have any snapshots"));
4627 return E_FAIL;
4628 }
4629
4630 if (aId.isEmpty())
4631 aSnapshot = mData->mFirstSnapshot;
4632 else
4633 aSnapshot = mData->mFirstSnapshot->findChildOrSelf (aId);
4634
4635 if (!aSnapshot)
4636 {
4637 if (aSetError)
4638 return setError (E_FAIL,
4639 tr ("Could not find a snapshot with UUID {%s}"),
4640 aId.toString().raw());
4641 return E_FAIL;
4642 }
4643
4644 return S_OK;
4645}
4646
4647/**
4648 * Returns the snapshot with the given name or fails of no such snapshot.
4649 *
4650 * @param aName snapshot name to find
4651 * @param aSnapshot where to return the found snapshot
4652 * @param aSetError true to set extended error info on failure
4653 */
4654HRESULT Machine::findSnapshot (const BSTR aName, ComObjPtr <Snapshot> &aSnapshot,
4655 bool aSetError /* = false */)
4656{
4657 AssertReturn (aName, E_INVALIDARG);
4658
4659 if (!mData->mFirstSnapshot)
4660 {
4661 if (aSetError)
4662 return setError (E_FAIL,
4663 tr ("This machine does not have any snapshots"));
4664 return E_FAIL;
4665 }
4666
4667 aSnapshot = mData->mFirstSnapshot->findChildOrSelf (aName);
4668
4669 if (!aSnapshot)
4670 {
4671 if (aSetError)
4672 return setError (E_FAIL,
4673 tr ("Could not find a snapshot named '%ls'"), aName);
4674 return E_FAIL;
4675 }
4676
4677 return S_OK;
4678}
4679
4680/**
4681 * Searches for an attachment that contains the given hard disk.
4682 * The hard disk must be associated with some VM and can be optionally
4683 * associated with some snapshot. If the attachment is stored in the snapshot
4684 * (i.e. the hard disk is associated with some snapshot), @a aSnapshot
4685 * will point to a non-null object on output.
4686 *
4687 * @param aHd hard disk to search an attachment for
4688 * @param aMachine where to store the hard disk's machine (can be NULL)
4689 * @param aSnapshot where to store the hard disk's snapshot (can be NULL)
4690 * @param aHda where to store the hard disk's attachment (can be NULL)
4691 *
4692 *
4693 * @note
4694 * It is assumed that the machine where the attachment is found,
4695 * is already placed to the Discarding state, when this method is called.
4696 * @note
4697 * The object returned in @a aHda is the attachment from the snapshot
4698 * machine if the hard disk is associated with the snapshot, not from the
4699 * primary machine object returned returned in @a aMachine.
4700 */
4701HRESULT Machine::findHardDiskAttachment (const ComObjPtr <HardDisk> &aHd,
4702 ComObjPtr <Machine> *aMachine,
4703 ComObjPtr <Snapshot> *aSnapshot,
4704 ComObjPtr <HardDiskAttachment> *aHda)
4705{
4706 AssertReturn (!aHd.isNull(), E_INVALIDARG);
4707
4708 Guid mid = aHd->machineId();
4709 Guid sid = aHd->snapshotId();
4710
4711 AssertReturn (!mid.isEmpty(), E_INVALIDARG);
4712
4713 ComObjPtr <Machine> m;
4714 mParent->getMachine (mid, m);
4715 ComAssertRet (!m.isNull(), E_FAIL);
4716
4717 HDData::HDAttachmentList *attachments = &m->mHDData->mHDAttachments;
4718
4719 ComObjPtr <Snapshot> s;
4720 if (!sid.isEmpty())
4721 {
4722 m->findSnapshot (sid, s);
4723 ComAssertRet (!s.isNull(), E_FAIL);
4724 attachments = &s->data().mMachine->mHDData->mHDAttachments;
4725 }
4726
4727 AssertReturn (attachments, E_FAIL);
4728
4729 for (HDData::HDAttachmentList::const_iterator it = attachments->begin();
4730 it != attachments->end();
4731 ++ it)
4732 {
4733 if ((*it)->hardDisk() == aHd)
4734 {
4735 if (aMachine) *aMachine = m;
4736 if (aSnapshot) *aSnapshot = s;
4737 if (aHda) *aHda = (*it);
4738 return S_OK;
4739 }
4740 }
4741
4742 ComAssertFailed();
4743 return E_FAIL;
4744}
4745
4746/**
4747 * Helper for #saveSettings. Cares about renaming the settings directory and
4748 * file if the machine name was changed and about creating a new settings file
4749 * if this is a new machine.
4750 *
4751 * @note Must be never called directly.
4752 *
4753 * @param aRenamed receives |true| if the name was changed and the settings
4754 * file was renamed as a result, or |false| otherwise. The
4755 * value makes sense only on success.
4756 * @param aNew receives |true| if a virgin settings file was created.
4757 */
4758HRESULT Machine::prepareSaveSettings (bool &aRenamed, bool &aNew)
4759{
4760 HRESULT rc = S_OK;
4761
4762 aRenamed = false;
4763
4764 /* if we're ready and isConfigLocked() is FALSE then it means
4765 * that no config file exists yet (we will create a virgin one) */
4766 aNew = !isConfigLocked();
4767
4768 /* attempt to rename the settings file if machine name is changed */
4769 if (mUserData->mNameSync &&
4770 mUserData.isBackedUp() &&
4771 mUserData.backedUpData()->mName != mUserData->mName)
4772 {
4773 aRenamed = true;
4774
4775 if (!aNew)
4776 {
4777 /* unlock the old config file */
4778 rc = unlockConfig();
4779 CheckComRCReturnRC (rc);
4780 }
4781
4782 bool dirRenamed = false;
4783 bool fileRenamed = false;
4784
4785 Utf8Str configFile, newConfigFile;
4786 Utf8Str configDir, newConfigDir;
4787
4788 do
4789 {
4790 int vrc = VINF_SUCCESS;
4791
4792 Utf8Str name = mUserData.backedUpData()->mName;
4793 Utf8Str newName = mUserData->mName;
4794
4795 configFile = mData->mConfigFileFull;
4796
4797 /* first, rename the directory if it matches the machine name */
4798 configDir = configFile;
4799 RTPathStripFilename (configDir.mutableRaw());
4800 newConfigDir = configDir;
4801 if (RTPathFilename (configDir) == name)
4802 {
4803 RTPathStripFilename (newConfigDir.mutableRaw());
4804 newConfigDir = Utf8StrFmt ("%s%c%s",
4805 newConfigDir.raw(), RTPATH_DELIMITER, newName.raw());
4806 /* new dir and old dir cannot be equal here because of 'if'
4807 * above and because name != newName */
4808 Assert (configDir != newConfigDir);
4809 if (!aNew)
4810 {
4811 /* perform real rename only if the machine is not new */
4812 vrc = RTPathRename (configDir.raw(), newConfigDir.raw(), 0);
4813 if (VBOX_FAILURE (vrc))
4814 {
4815 rc = setError (E_FAIL,
4816 tr ("Could not rename the directory '%s' to '%s' "
4817 "to save the settings file (%Vrc)"),
4818 configDir.raw(), newConfigDir.raw(), vrc);
4819 break;
4820 }
4821 dirRenamed = true;
4822 }
4823 }
4824
4825 newConfigFile = Utf8StrFmt ("%s%c%s.xml",
4826 newConfigDir.raw(), RTPATH_DELIMITER, newName.raw());
4827
4828 /* then try to rename the settings file itself */
4829 if (newConfigFile != configFile)
4830 {
4831 /* get the path to old settings file in renamed directory */
4832 configFile = Utf8StrFmt ("%s%c%s",
4833 newConfigDir.raw(), RTPATH_DELIMITER,
4834 RTPathFilename (configFile));
4835 if (!aNew)
4836 {
4837 /* perform real rename only if the machine is not new */
4838 vrc = RTFileRename (configFile.raw(), newConfigFile.raw(), 0);
4839 if (VBOX_FAILURE (vrc))
4840 {
4841 rc = setError (E_FAIL,
4842 tr ("Could not rename the settings file '%s' to '%s' "
4843 "(%Vrc)"),
4844 configFile.raw(), newConfigFile.raw(), vrc);
4845 break;
4846 }
4847 fileRenamed = true;
4848 }
4849 }
4850
4851 /* update mConfigFileFull amd mConfigFile */
4852 Bstr oldConfigFileFull = mData->mConfigFileFull;
4853 Bstr oldConfigFile = mData->mConfigFile;
4854 mData->mConfigFileFull = newConfigFile;
4855 /* try to get the relative path for mConfigFile */
4856 Utf8Str path = newConfigFile;
4857 mParent->calculateRelativePath (path, path);
4858 mData->mConfigFile = path;
4859
4860 /* last, try to update the global settings with the new path */
4861 if (mData->mRegistered)
4862 {
4863 rc = mParent->updateSettings (configDir, newConfigDir);
4864 if (FAILED (rc))
4865 {
4866 /* revert to old values */
4867 mData->mConfigFileFull = oldConfigFileFull;
4868 mData->mConfigFile = oldConfigFile;
4869 break;
4870 }
4871 }
4872
4873 /* update the snapshot folder */
4874 path = mUserData->mSnapshotFolderFull;
4875 if (RTPathStartsWith (path, configDir))
4876 {
4877 path = Utf8StrFmt ("%s%s", newConfigDir.raw(),
4878 path.raw() + configDir.length());
4879 mUserData->mSnapshotFolderFull = path;
4880 calculateRelativePath (path, path);
4881 mUserData->mSnapshotFolder = path;
4882 }
4883
4884 /* update the saved state file path */
4885 path = mSSData->mStateFilePath;
4886 if (RTPathStartsWith (path, configDir))
4887 {
4888 path = Utf8StrFmt ("%s%s", newConfigDir.raw(),
4889 path.raw() + configDir.length());
4890 mSSData->mStateFilePath = path;
4891 }
4892
4893 /* Update saved state file paths of all online snapshots.
4894 * Note that saveSettings() will recognize name change
4895 * and will save all snapshots in this case. */
4896 if (mData->mFirstSnapshot)
4897 mData->mFirstSnapshot->updateSavedStatePaths (configDir,
4898 newConfigDir);
4899 }
4900 while (0);
4901
4902 if (FAILED (rc))
4903 {
4904 /* silently try to rename everything back */
4905 if (fileRenamed)
4906 RTFileRename (newConfigFile.raw(), configFile.raw(), 0);
4907 if (dirRenamed)
4908 RTPathRename (newConfigDir.raw(), configDir.raw(), 0);
4909 }
4910
4911 if (!aNew)
4912 {
4913 /* lock the config again */
4914 HRESULT rc2 = lockConfig();
4915 if (SUCCEEDED (rc))
4916 rc = rc2;
4917 }
4918
4919 CheckComRCReturnRC (rc);
4920 }
4921
4922 if (aNew)
4923 {
4924 /* create a virgin config file */
4925 int vrc = VINF_SUCCESS;
4926
4927 /* ensure the settings directory exists */
4928 Utf8Str path = mData->mConfigFileFull;
4929 RTPathStripFilename (path.mutableRaw());
4930 if (!RTDirExists (path))
4931 {
4932 vrc = RTDirCreateFullPath (path, 0777);
4933 if (VBOX_FAILURE (vrc))
4934 {
4935 return setError (E_FAIL,
4936 tr ("Could not create a directory '%s' "
4937 "to save the settings file (%Vrc)"),
4938 path.raw(), vrc);
4939 }
4940 }
4941
4942 /* Note: open flags must correlated with RTFileOpen() in lockConfig() */
4943 path = Utf8Str (mData->mConfigFileFull);
4944 vrc = RTFileOpen (&mData->mHandleCfgFile, path,
4945 RTFILE_O_READWRITE | RTFILE_O_CREATE |
4946 RTFILE_O_DENY_WRITE);
4947 if (VBOX_SUCCESS (vrc))
4948 {
4949 vrc = RTFileWrite (mData->mHandleCfgFile,
4950 (void *) DefaultMachineConfig,
4951 sizeof (DefaultMachineConfig), NULL);
4952 }
4953 if (VBOX_FAILURE (vrc))
4954 {
4955 mData->mHandleCfgFile = NIL_RTFILE;
4956 return setError (E_FAIL,
4957 tr ("Could not create the settings file '%s' (%Vrc)"),
4958 path.raw(), vrc);
4959 }
4960 /* we do not close the file to simulate lockConfig() */
4961 }
4962
4963 return rc;
4964}
4965
4966/**
4967 * Saves machine data, user data and hardware data.
4968 *
4969 * @param aMarkCurStateAsModified
4970 * if true (default), mData->mCurrentStateModified will be set to
4971 * what #isReallyModified() returns prior to saving settings to a file,
4972 * otherwise the current value of mData->mCurrentStateModified will be
4973 * saved.
4974 * @param aInformCallbacksAnyway
4975 * if true, callbacks will be informed even if #isReallyModified()
4976 * returns false. This is necessary for cases when we change machine data
4977 * diectly, not through the backup()/commit() mechanism.
4978 *
4979 * @note Locks mParent (only in some cases, and only when #isConfigLocked() is
4980 * |TRUE|, see the #prepareSaveSettings() code for details) +
4981 * this object + children for writing.
4982 */
4983HRESULT Machine::saveSettings (bool aMarkCurStateAsModified /* = true */,
4984 bool aInformCallbacksAnyway /* = false */)
4985{
4986 LogFlowThisFuncEnter();
4987
4988 /// @todo (dmik) I guess we should lock all our child objects here
4989 // (such as mVRDPServer etc.) to ensure they are not changed
4990 // until completely saved to disk and committed
4991
4992 /// @todo (dmik) also, we need to delegate saving child objects' settings
4993 // to objects themselves to ensure operations 'commit + save changes'
4994 // are atomic (amd done from the object's lock so that nobody can change
4995 // settings again until completely saved).
4996
4997 AssertReturn (mType == IsMachine || mType == IsSessionMachine, E_FAIL);
4998
4999 bool wasModified;
5000
5001 if (aMarkCurStateAsModified)
5002 {
5003 /*
5004 * We ignore changes to user data when setting mCurrentStateModified
5005 * because the current state will not differ from the current snapshot
5006 * if only user data has been changed (user data is shared by all
5007 * snapshots).
5008 */
5009 mData->mCurrentStateModified = isReallyModified (true /* aIgnoreUserData */);
5010 wasModified = mUserData.hasActualChanges() || mData->mCurrentStateModified;
5011 }
5012 else
5013 {
5014 wasModified = isReallyModified();
5015 }
5016
5017 HRESULT rc = S_OK;
5018
5019 /* First, prepare to save settings. It will will care about renaming the
5020 * settings directory and file if the machine name was changed and about
5021 * creating a new settings file if this is a new machine. */
5022 bool isRenamed = false;
5023 bool isNew = false;
5024 rc = prepareSaveSettings (isRenamed, isNew);
5025 CheckComRCReturnRC (rc);
5026
5027 /* then, open the settings file */
5028 CFGHANDLE configLoader = 0;
5029 rc = openConfigLoader (&configLoader, isNew);
5030 CheckComRCReturnRC (rc);
5031
5032 /* save all snapshots when the machine name was changed since
5033 * it may affect saved state file paths for online snapshots (see
5034 * #openConfigLoader() for details) */
5035 bool updateAllSnapshots = isRenamed;
5036
5037 /* commit before saving, since it may change settings
5038 * (for example, perform fixup of lazy hard disk changes) */
5039 rc = commit();
5040 if (FAILED (rc))
5041 {
5042 closeConfigLoader (configLoader, false /* aSaveBeforeClose */);
5043 return rc;
5044 }
5045
5046 /* include hard disk changes to the modified flag */
5047 wasModified |= mHDData->mHDAttachmentsChanged;
5048 if (aMarkCurStateAsModified)
5049 mData->mCurrentStateModified |= BOOL (mHDData->mHDAttachmentsChanged);
5050
5051
5052 CFGNODE machineNode = 0;
5053 /* create if not exists */
5054 CFGLDRCreateNode (configLoader, "VirtualBox/Machine", &machineNode);
5055
5056 do
5057 {
5058 ComAssertBreak (machineNode, rc = E_FAIL);
5059
5060 /* uuid (required) */
5061 Assert (mData->mUuid);
5062 CFGLDRSetUUID (machineNode, "uuid", mData->mUuid.raw());
5063
5064 /* name (required) */
5065 Assert (!mUserData->mName.isEmpty());
5066 CFGLDRSetBSTR (machineNode, "name", mUserData->mName);
5067
5068 /* nameSync (optional, default is true) */
5069 if (!mUserData->mNameSync)
5070 CFGLDRSetBool (machineNode, "nameSync", false);
5071 else
5072 CFGLDRDeleteAttribute (machineNode, "nameSync");
5073
5074 /* Description node (optional) */
5075 if (!mUserData->mDescription.isNull())
5076 {
5077 CFGNODE descNode = 0;
5078 CFGLDRCreateChildNode (machineNode, "Description", &descNode);
5079 Assert (descNode);
5080 CFGLDRSetBSTR (descNode, NULL, mUserData->mDescription);
5081 CFGLDRReleaseNode (descNode);
5082 }
5083 else
5084 {
5085 CFGNODE descNode = 0;
5086 CFGLDRGetChildNode (machineNode, "Description", 0, &descNode);
5087 if (descNode)
5088 CFGLDRDeleteNode (descNode);
5089 }
5090
5091 /* OSType (required) */
5092 {
5093 CFGLDRSetBSTR (machineNode, "OSType", mUserData->mOSTypeId);
5094 }
5095
5096 /* stateFile (optional) */
5097 if (mData->mMachineState == MachineState_Saved)
5098 {
5099 Assert (!mSSData->mStateFilePath.isEmpty());
5100 /* try to make the file name relative to the settings file dir */
5101 Utf8Str stateFilePath = mSSData->mStateFilePath;
5102 calculateRelativePath (stateFilePath, stateFilePath);
5103 CFGLDRSetString (machineNode, "stateFile", stateFilePath);
5104 }
5105 else
5106 {
5107 Assert (mSSData->mStateFilePath.isNull());
5108 CFGLDRDeleteAttribute (machineNode, "stateFile");
5109 }
5110
5111 /* currentSnapshot ID (optional) */
5112 if (!mData->mCurrentSnapshot.isNull())
5113 {
5114 Assert (!mData->mFirstSnapshot.isNull());
5115 CFGLDRSetUUID (machineNode, "currentSnapshot",
5116 mData->mCurrentSnapshot->data().mId);
5117 }
5118 else
5119 {
5120 Assert (mData->mFirstSnapshot.isNull());
5121 CFGLDRDeleteAttribute (machineNode, "currentSnapshot");
5122 }
5123
5124 /* snapshotFolder (optional) */
5125 if (!mUserData->mSnapshotFolder.isEmpty())
5126 CFGLDRSetBSTR (machineNode, "snapshotFolder", mUserData->mSnapshotFolder);
5127 else
5128 CFGLDRDeleteAttribute (machineNode, "snapshotFolder");
5129
5130 /* currentStateModified (optional, default is yes) */
5131 if (!mData->mCurrentStateModified)
5132 CFGLDRSetBool (machineNode, "currentStateModified", false);
5133 else
5134 CFGLDRDeleteAttribute (machineNode, "currentStateModified");
5135
5136 /* lastStateChange */
5137 CFGLDRSetDateTime (machineNode, "lastStateChange",
5138 mData->mLastStateChange);
5139
5140 /* Hardware node (required) */
5141 {
5142 CFGNODE hwNode = 0;
5143 CFGLDRGetChildNode (machineNode, "Hardware", 0, &hwNode);
5144 /* first, delete the entire node if exists */
5145 if (hwNode)
5146 CFGLDRDeleteNode (hwNode);
5147 /* then recreate it */
5148 hwNode = 0;
5149 CFGLDRCreateChildNode (machineNode, "Hardware", &hwNode);
5150 ComAssertBreak (hwNode, rc = E_FAIL);
5151
5152 rc = saveHardware (hwNode);
5153
5154 CFGLDRReleaseNode (hwNode);
5155 if (FAILED (rc))
5156 break;
5157 }
5158
5159 /* HardDiskAttachments node (required) */
5160 {
5161 CFGNODE hdasNode = 0;
5162 CFGLDRGetChildNode (machineNode, "HardDiskAttachments", 0, &hdasNode);
5163 /* first, delete the entire node if exists */
5164 if (hdasNode)
5165 CFGLDRDeleteNode (hdasNode);
5166 /* then recreate it */
5167 hdasNode = 0;
5168 CFGLDRCreateChildNode (machineNode, "HardDiskAttachments", &hdasNode);
5169 ComAssertBreak (hdasNode, rc = E_FAIL);
5170
5171 rc = saveHardDisks (hdasNode);
5172
5173 CFGLDRReleaseNode (hdasNode);
5174 if (FAILED (rc))
5175 break;
5176 }
5177
5178 /* update all snapshots if requested */
5179 if (updateAllSnapshots)
5180 rc = saveSnapshotSettingsWorker (machineNode, NULL,
5181 SaveSS_UpdateAllOp);
5182 }
5183 while (0);
5184
5185 if (machineNode)
5186 CFGLDRReleaseNode (machineNode);
5187
5188 if (SUCCEEDED (rc))
5189 rc = closeConfigLoader (configLoader, true /* aSaveBeforeClose */);
5190 else
5191 closeConfigLoader (configLoader, false /* aSaveBeforeClose */);
5192
5193 if (FAILED (rc))
5194 {
5195 /*
5196 * backup arbitrary data item to cause #isModified() to still return
5197 * true in case of any error
5198 */
5199 mHWData.backup();
5200 }
5201
5202 if (wasModified || aInformCallbacksAnyway)
5203 {
5204 /*
5205 * Fire the data change event, even on failure (since we've already
5206 * committed all data). This is done only for SessionMachines because
5207 * mutable Machine instances are always not registered (i.e. private
5208 * to the client process that creates them) and thus don't need to
5209 * inform callbacks.
5210 */
5211 if (mType == IsSessionMachine)
5212 mParent->onMachineDataChange (mData->mUuid);
5213 }
5214
5215 LogFlowThisFunc (("rc=%08X\n", rc));
5216 LogFlowThisFuncLeave();
5217 return rc;
5218}
5219
5220/**
5221 * Wrapper for #saveSnapshotSettingsWorker() that opens the settings file
5222 * and locates the <Machine> node in there. See #saveSnapshotSettingsWorker()
5223 * for more details.
5224 *
5225 * @param aSnapshot Snapshot to operate on
5226 * @param aOpFlags Operation to perform, one of SaveSS_NoOp, SaveSS_AddOp
5227 * or SaveSS_UpdateAttrsOp possibly combined with
5228 * SaveSS_UpdateCurrentId.
5229 *
5230 * @note Locks this object for writing + other child objects.
5231 */
5232HRESULT Machine::saveSnapshotSettings (Snapshot *aSnapshot, int aOpFlags)
5233{
5234 AutoCaller autoCaller (this);
5235 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
5236
5237 AssertReturn (mType == IsMachine || mType == IsSessionMachine, E_FAIL);
5238
5239 AutoLock alock (this);
5240
5241 AssertReturn (isConfigLocked(), E_FAIL);
5242
5243 HRESULT rc = S_OK;
5244
5245 /* load the config file */
5246 CFGHANDLE configLoader = 0;
5247 rc = openConfigLoader (&configLoader);
5248 if (FAILED (rc))
5249 return rc;
5250
5251 CFGNODE machineNode = 0;
5252 CFGLDRGetNode (configLoader, "VirtualBox/Machine", 0, &machineNode);
5253
5254 do
5255 {
5256 ComAssertBreak (machineNode, rc = E_FAIL);
5257
5258 rc = saveSnapshotSettingsWorker (machineNode, aSnapshot, aOpFlags);
5259
5260 CFGLDRReleaseNode (machineNode);
5261 }
5262 while (0);
5263
5264 if (SUCCEEDED (rc))
5265 rc = closeConfigLoader (configLoader, true /* aSaveBeforeClose */);
5266 else
5267 closeConfigLoader (configLoader, false /* aSaveBeforeClose */);
5268
5269 return rc;
5270}
5271
5272/**
5273 * Performs the specified operation on the given snapshot
5274 * in the settings file represented by \a aMachineNode.
5275 *
5276 * If \a aOpFlags = SaveSS_UpdateAllOp, \a aSnapshot can be NULL to indicate
5277 * that the whole tree of the snapshots should be updated in <Machine>.
5278 * One particular case is when the last (and the only) snapshot should be
5279 * removed (it is so when both mCurrentSnapshot and mFirstSnapshot are NULL).
5280 *
5281 * \a aOp may be just SaveSS_UpdateCurrentId if only the currentSnapshot
5282 * attribute of <Machine> needs to be updated.
5283 *
5284 * @param aMachineNode <Machine> node in the opened settings file
5285 * @param aSnapshot Snapshot to operate on
5286 * @param aOpFlags Operation to perform, one of SaveSS_NoOp, SaveSS_AddOp
5287 * or SaveSS_UpdateAttrsOp possibly combined with
5288 * SaveSS_UpdateCurrentId.
5289 *
5290 * @note Must be called with this object locked for writing.
5291 * Locks child objects.
5292 */
5293HRESULT Machine::saveSnapshotSettingsWorker (CFGNODE aMachineNode,
5294 Snapshot *aSnapshot, int aOpFlags)
5295{
5296 AssertReturn (aMachineNode, E_FAIL);
5297
5298 AssertReturn (isLockedOnCurrentThread(), E_FAIL);
5299
5300 int op = aOpFlags & SaveSS_OpMask;
5301 AssertReturn (
5302 (aSnapshot && (op == SaveSS_AddOp || op == SaveSS_UpdateAttrsOp ||
5303 op == SaveSS_UpdateAllOp)) ||
5304 (!aSnapshot && ((op == SaveSS_NoOp && (aOpFlags & SaveSS_UpdateCurrentId)) ||
5305 op == SaveSS_UpdateAllOp)),
5306 E_FAIL);
5307
5308 HRESULT rc = S_OK;
5309
5310 bool recreateWholeTree = false;
5311
5312 do
5313 {
5314 if (op == SaveSS_NoOp)
5315 break;
5316
5317 /* quick path: recreate the whole tree of the snapshots */
5318 if (op == SaveSS_UpdateAllOp && !aSnapshot)
5319 {
5320 /* first, delete the entire root snapshot node if it exists */
5321 CFGNODE snapshotNode = 0;
5322 CFGLDRGetChildNode (aMachineNode, "Snapshot", 0, &snapshotNode);
5323 if (snapshotNode)
5324 CFGLDRDeleteNode (snapshotNode);
5325
5326 /*
5327 * second, if we have any snapshots left, substitute aSnapshot with
5328 * the first snapshot to recreate the whole tree, otherwise break
5329 */
5330 if (mData->mFirstSnapshot)
5331 {
5332 aSnapshot = mData->mFirstSnapshot;
5333 recreateWholeTree = true;
5334 }
5335 else
5336 break;
5337 }
5338
5339 Assert (!!aSnapshot);
5340 ComObjPtr <Snapshot> parent = aSnapshot->parent();
5341
5342 if (op == SaveSS_AddOp)
5343 {
5344 CFGNODE parentNode = 0;
5345
5346 if (parent)
5347 {
5348 rc = findSnapshotNode (parent, aMachineNode, NULL, &parentNode);
5349 if (FAILED (rc))
5350 break;
5351 ComAssertBreak (parentNode, rc = E_FAIL);
5352 }
5353
5354 do
5355 {
5356 CFGNODE snapshotsNode = 0;
5357
5358 if (parentNode)
5359 {
5360 CFGLDRCreateChildNode (parentNode, "Snapshots", &snapshotsNode);
5361 ComAssertBreak (snapshotsNode, rc = E_FAIL);
5362 }
5363 else
5364 snapshotsNode = aMachineNode;
5365 do
5366 {
5367 CFGNODE snapshotNode = 0;
5368 CFGLDRAppendChildNode (snapshotsNode, "Snapshot", &snapshotNode);
5369 ComAssertBreak (snapshotNode, rc = E_FAIL);
5370 rc = saveSnapshot (snapshotNode, aSnapshot, false /* aAttrsOnly */);
5371 CFGLDRReleaseNode (snapshotNode);
5372
5373 if (FAILED (rc))
5374 break;
5375
5376 /*
5377 * when a new snapshot is added, this means diffs were created
5378 * for every normal/immutable hard disk of the VM, so we need to
5379 * save the current hard disk attachments
5380 */
5381
5382 CFGNODE hdasNode = 0;
5383 CFGLDRGetChildNode (aMachineNode, "HardDiskAttachments", 0, &hdasNode);
5384 if (hdasNode)
5385 CFGLDRDeleteNode (hdasNode);
5386 CFGLDRCreateChildNode (aMachineNode, "HardDiskAttachments", &hdasNode);
5387 ComAssertBreak (hdasNode, rc = E_FAIL);
5388
5389 rc = saveHardDisks (hdasNode);
5390
5391 if (mHDData->mHDAttachments.size() != 0)
5392 {
5393 /*
5394 * If we have one or more attachments then we definitely
5395 * created diffs for them and associated new diffs with
5396 * current settngs. So, since we don't use saveSettings(),
5397 * we need to inform callbacks manually.
5398 */
5399 if (mType == IsSessionMachine)
5400 mParent->onMachineDataChange (mData->mUuid);
5401 }
5402
5403 CFGLDRReleaseNode (hdasNode);
5404 }
5405 while (0);
5406
5407 if (snapshotsNode != aMachineNode)
5408 CFGLDRReleaseNode (snapshotsNode);
5409 }
5410 while (0);
5411
5412 if (parentNode)
5413 CFGLDRReleaseNode (parentNode);
5414
5415 break;
5416 }
5417
5418 Assert (op == SaveSS_UpdateAttrsOp && !recreateWholeTree ||
5419 op == SaveSS_UpdateAllOp);
5420
5421 CFGNODE snapshotsNode = 0;
5422 CFGNODE snapshotNode = 0;
5423
5424 if (!recreateWholeTree)
5425 {
5426 rc = findSnapshotNode (aSnapshot, aMachineNode,
5427 &snapshotsNode, &snapshotNode);
5428 if (FAILED (rc))
5429 break;
5430 ComAssertBreak (snapshotNode, rc = E_FAIL);
5431 }
5432
5433 if (!snapshotsNode)
5434 snapshotsNode = aMachineNode;
5435
5436 if (op == SaveSS_UpdateAttrsOp)
5437 rc = saveSnapshot (snapshotNode, aSnapshot, true /* aAttrsOnly */);
5438 else do
5439 {
5440 if (snapshotNode)
5441 {
5442 CFGLDRDeleteNode (snapshotNode);
5443 snapshotNode = 0;
5444 }
5445 CFGLDRAppendChildNode (snapshotsNode, "Snapshot", &snapshotNode);
5446 ComAssertBreak (snapshotNode, rc = E_FAIL);
5447 rc = saveSnapshot (snapshotNode, aSnapshot, false /* aAttrsOnly */);
5448 }
5449 while (0);
5450
5451 CFGLDRReleaseNode (snapshotNode);
5452 if (snapshotsNode != aMachineNode)
5453 CFGLDRReleaseNode (snapshotsNode);
5454 }
5455 while (0);
5456
5457 if (SUCCEEDED (rc))
5458 {
5459 /* update currentSnapshot when appropriate */
5460 if (aOpFlags & SaveSS_UpdateCurrentId)
5461 {
5462 if (!mData->mCurrentSnapshot.isNull())
5463 CFGLDRSetUUID (aMachineNode, "currentSnapshot",
5464 mData->mCurrentSnapshot->data().mId);
5465 else
5466 CFGLDRDeleteAttribute (aMachineNode, "currentSnapshot");
5467 }
5468 if (aOpFlags & SaveSS_UpdateCurStateModified)
5469 {
5470 if (!mData->mCurrentStateModified)
5471 CFGLDRSetBool (aMachineNode, "currentStateModified", false);
5472 else
5473 CFGLDRDeleteAttribute (aMachineNode, "currentStateModified");
5474 }
5475 }
5476
5477 return rc;
5478}
5479
5480/**
5481 * Saves the given snapshot and all its children (unless \a aAttrsOnly is true).
5482 * It is assumed that the given node is empty (unless \a aAttrsOnly is true).
5483 *
5484 * @param aNode <Snapshot> node to save the snapshot to
5485 * @param aSnapshot snapshot to save
5486 * @param aAttrsOnly if true, only updatge user-changeable attrs
5487 */
5488HRESULT Machine::saveSnapshot (CFGNODE aNode, Snapshot *aSnapshot, bool aAttrsOnly)
5489{
5490 AssertReturn (aNode && aSnapshot, E_INVALIDARG);
5491 AssertReturn (mType == IsMachine || mType == IsSessionMachine, E_FAIL);
5492
5493 /* uuid (required) */
5494 if (!aAttrsOnly)
5495 CFGLDRSetUUID (aNode, "uuid", aSnapshot->data().mId);
5496
5497 /* name (required) */
5498 CFGLDRSetBSTR (aNode, "name", aSnapshot->data().mName);
5499
5500 /* timeStamp (required) */
5501 CFGLDRSetDateTime (aNode, "timeStamp", aSnapshot->data().mTimeStamp);
5502
5503 /* Description node (optional) */
5504 if (!aSnapshot->data().mDescription.isNull())
5505 {
5506 CFGNODE descNode = 0;
5507 CFGLDRCreateChildNode (aNode, "Description", &descNode);
5508 Assert (descNode);
5509 CFGLDRSetBSTR (descNode, NULL, aSnapshot->data().mDescription);
5510 CFGLDRReleaseNode (descNode);
5511 }
5512 else
5513 {
5514 CFGNODE descNode = 0;
5515 CFGLDRGetChildNode (aNode, "Description", 0, &descNode);
5516 if (descNode)
5517 CFGLDRDeleteNode (descNode);
5518 }
5519
5520 if (aAttrsOnly)
5521 return S_OK;
5522
5523 /* stateFile (optional) */
5524 if (aSnapshot->stateFilePath())
5525 {
5526 /* try to make the file name relative to the settings file dir */
5527 Utf8Str stateFilePath = aSnapshot->stateFilePath();
5528 calculateRelativePath (stateFilePath, stateFilePath);
5529 CFGLDRSetString (aNode, "stateFile", stateFilePath);
5530 }
5531
5532 {
5533 ComObjPtr <SnapshotMachine> snapshotMachine = aSnapshot->data().mMachine;
5534 ComAssertRet (!snapshotMachine.isNull(), E_FAIL);
5535
5536 /* save hardware */
5537 {
5538 CFGNODE hwNode = 0;
5539 CFGLDRCreateChildNode (aNode, "Hardware", &hwNode);
5540
5541 HRESULT rc = snapshotMachine->saveHardware (hwNode);
5542
5543 CFGLDRReleaseNode (hwNode);
5544 if (FAILED (rc))
5545 return rc;
5546 }
5547
5548 /* save hard disks */
5549 {
5550 CFGNODE hdasNode = 0;
5551 CFGLDRCreateChildNode (aNode, "HardDiskAttachments", &hdasNode);
5552
5553 HRESULT rc = snapshotMachine->saveHardDisks (hdasNode);
5554
5555 CFGLDRReleaseNode (hdasNode);
5556 if (FAILED (rc))
5557 return rc;
5558 }
5559 }
5560
5561 /* save children */
5562 {
5563 AutoLock listLock (aSnapshot->childrenLock());
5564
5565 if (aSnapshot->children().size())
5566 {
5567 CFGNODE snapshotsNode = 0;
5568 CFGLDRCreateChildNode (aNode, "Snapshots", &snapshotsNode);
5569
5570 HRESULT rc = S_OK;
5571
5572 for (Snapshot::SnapshotList::const_iterator it = aSnapshot->children().begin();
5573 it != aSnapshot->children().end() && SUCCEEDED (rc);
5574 ++ it)
5575 {
5576 CFGNODE snapshotNode = 0;
5577 CFGLDRCreateChildNode (snapshotsNode, "Snapshot", &snapshotNode);
5578
5579 rc = saveSnapshot (snapshotNode, (*it), aAttrsOnly);
5580
5581 CFGLDRReleaseNode (snapshotNode);
5582 }
5583
5584 CFGLDRReleaseNode (snapshotsNode);
5585 if (FAILED (rc))
5586 return rc;
5587 }
5588 }
5589
5590 return S_OK;
5591}
5592
5593/**
5594 * Creates Saves the VM hardware configuration.
5595 * It is assumed that the given node is empty.
5596 *
5597 * @param aNode <Hardware> node to save the VM hardware confguration to
5598 */
5599HRESULT Machine::saveHardware (CFGNODE aNode)
5600{
5601 AssertReturn (aNode, E_INVALIDARG);
5602
5603 HRESULT rc = S_OK;
5604
5605 /* CPU */
5606 {
5607 CFGNODE cpuNode = 0;
5608 CFGLDRCreateChildNode (aNode, "CPU", &cpuNode);
5609 CFGNODE hwVirtExNode = 0;
5610 CFGLDRCreateChildNode (cpuNode, "HardwareVirtEx", &hwVirtExNode);
5611 const char *value = NULL;
5612 switch (mHWData->mHWVirtExEnabled)
5613 {
5614 case TriStateBool_False:
5615 value = "false";
5616 break;
5617 case TriStateBool_True:
5618 value = "true";
5619 break;
5620 default:
5621 value = "default";
5622 }
5623 CFGLDRSetString (hwVirtExNode, "enabled", value);
5624 CFGLDRReleaseNode (hwVirtExNode);
5625 CFGLDRReleaseNode (cpuNode);
5626 }
5627
5628 /* memory (required) */
5629 {
5630 CFGNODE memoryNode = 0;
5631 CFGLDRCreateChildNode (aNode, "Memory", &memoryNode);
5632 CFGLDRSetUInt32 (memoryNode, "RAMSize", mHWData->mMemorySize);
5633 CFGLDRReleaseNode (memoryNode);
5634 }
5635
5636 /* boot (required) */
5637 do
5638 {
5639 CFGNODE bootNode = 0;
5640 CFGLDRCreateChildNode (aNode, "Boot", &bootNode);
5641
5642 for (ULONG pos = 0; pos < ELEMENTS (mHWData->mBootOrder); pos ++)
5643 {
5644 const char *device = NULL;
5645 switch (mHWData->mBootOrder [pos])
5646 {
5647 case DeviceType_NoDevice:
5648 /* skip, this is allowed for <Order> nodes
5649 * when loading, the default value NoDevice will remain */
5650 continue;
5651 case DeviceType_FloppyDevice: device = "Floppy"; break;
5652 case DeviceType_DVDDevice: device = "DVD"; break;
5653 case DeviceType_HardDiskDevice: device = "HardDisk"; break;
5654 case DeviceType_NetworkDevice: device = "Network"; break;
5655 default:
5656 ComAssertMsgFailedBreak (("Invalid boot device: %d\n",
5657 mHWData->mBootOrder [pos]),
5658 rc = E_FAIL);
5659 }
5660 if (FAILED (rc))
5661 break;
5662
5663 CFGNODE orderNode = 0;
5664 CFGLDRAppendChildNode (bootNode, "Order", &orderNode);
5665
5666 CFGLDRSetUInt32 (orderNode, "position", pos + 1);
5667 CFGLDRSetString (orderNode, "device", device);
5668
5669 CFGLDRReleaseNode (orderNode);
5670 }
5671
5672 CFGLDRReleaseNode (bootNode);
5673 }
5674 while (0);
5675
5676 if (FAILED (rc))
5677 return rc;
5678
5679 /* display (required) */
5680 {
5681 CFGNODE displayNode = 0;
5682 CFGLDRCreateChildNode (aNode, "Display", &displayNode);
5683 CFGLDRSetUInt32 (displayNode, "VRAMSize", mHWData->mVRAMSize);
5684 CFGLDRSetUInt32 (displayNode, "MonitorCount", mHWData->mMonitorCount);
5685 CFGLDRReleaseNode (displayNode);
5686 }
5687
5688#ifdef VBOX_VRDP
5689 /* VRDP settings (optional) */
5690 /// @todo (dmik) move the code to VRDPServer
5691 /// @todo r=sunlover: moved. dmik, please review.
5692 {
5693 CFGNODE remoteDisplayNode = 0;
5694 CFGLDRCreateChildNode (aNode, "RemoteDisplay", &remoteDisplayNode);
5695
5696 if (remoteDisplayNode)
5697 {
5698 mVRDPServer->saveConfig (remoteDisplayNode);
5699 CFGLDRReleaseNode (remoteDisplayNode);
5700 }
5701 }
5702#endif
5703
5704 /* BIOS (required) */
5705 {
5706 CFGNODE biosNode = 0;
5707 CFGLDRCreateChildNode (aNode, "BIOS", &biosNode);
5708 {
5709 BOOL fSet;
5710 /* ACPI */
5711 CFGNODE acpiNode = 0;
5712 CFGLDRCreateChildNode (biosNode, "ACPI", &acpiNode);
5713 mBIOSSettings->COMGETTER(ACPIEnabled)(&fSet);
5714 CFGLDRSetBool (acpiNode, "enabled", !!fSet);
5715 CFGLDRReleaseNode (acpiNode);
5716
5717 /* IOAPIC */
5718 CFGNODE ioapicNode = 0;
5719 CFGLDRCreateChildNode (biosNode, "IOAPIC", &ioapicNode);
5720 mBIOSSettings->COMGETTER(IOAPICEnabled)(&fSet);
5721 CFGLDRSetBool (ioapicNode, "enabled", !!fSet);
5722 CFGLDRReleaseNode (ioapicNode);
5723
5724 /* BIOS logo (optional) **/
5725 CFGNODE logoNode = 0;
5726 CFGLDRCreateChildNode (biosNode, "Logo", &logoNode);
5727 mBIOSSettings->COMGETTER(LogoFadeIn)(&fSet);
5728 CFGLDRSetBool (logoNode, "fadeIn", !!fSet);
5729 mBIOSSettings->COMGETTER(LogoFadeOut)(&fSet);
5730 CFGLDRSetBool (logoNode, "fadeOut", !!fSet);
5731 ULONG ulDisplayTime;
5732 mBIOSSettings->COMGETTER(LogoDisplayTime)(&ulDisplayTime);
5733 CFGLDRSetUInt32 (logoNode, "displayTime", ulDisplayTime);
5734 Bstr logoPath;
5735 mBIOSSettings->COMGETTER(LogoImagePath)(logoPath.asOutParam());
5736 if (logoPath)
5737 CFGLDRSetBSTR (logoNode, "imagePath", logoPath);
5738 else
5739 CFGLDRDeleteAttribute (logoNode, "imagePath");
5740 CFGLDRReleaseNode (logoNode);
5741
5742 /* boot menu (optional) */
5743 CFGNODE bootMenuNode = 0;
5744 CFGLDRCreateChildNode (biosNode, "BootMenu", &bootMenuNode);
5745 BIOSBootMenuMode_T bootMenuMode;
5746 Bstr bootMenuModeStr;
5747 mBIOSSettings->COMGETTER(BootMenuMode)(&bootMenuMode);
5748 switch (bootMenuMode)
5749 {
5750 case BIOSBootMenuMode_Disabled:
5751 bootMenuModeStr = "disabled";
5752 break;
5753 case BIOSBootMenuMode_MenuOnly:
5754 bootMenuModeStr = "menuonly";
5755 break;
5756 default:
5757 bootMenuModeStr = "messageandmenu";
5758 }
5759 CFGLDRSetBSTR (bootMenuNode, "mode", bootMenuModeStr);
5760 CFGLDRReleaseNode (bootMenuNode);
5761
5762 /* time offset (optional) */
5763 CFGNODE timeOffsetNode = 0;
5764 CFGLDRCreateChildNode (biosNode, "TimeOffset", &timeOffsetNode);
5765 LONG64 timeOffset;
5766 mBIOSSettings->COMGETTER(TimeOffset)(&timeOffset);
5767 CFGLDRSetInt64 (timeOffsetNode, "value", timeOffset);
5768 CFGLDRReleaseNode (timeOffsetNode);
5769 }
5770 CFGLDRReleaseNode(biosNode);
5771 }
5772
5773 /* DVD drive (required) */
5774 /// @todo (dmik) move the code to DVDDrive
5775 do
5776 {
5777 CFGNODE dvdNode = 0;
5778 CFGLDRCreateChildNode (aNode, "DVDDrive", &dvdNode);
5779
5780 BOOL fPassthrough;
5781 mDVDDrive->COMGETTER(Passthrough)(&fPassthrough);
5782 CFGLDRSetBool(dvdNode, "passthrough", !!fPassthrough);
5783
5784 switch (mDVDDrive->data()->mDriveState)
5785 {
5786 case DriveState_ImageMounted:
5787 {
5788 Assert (!mDVDDrive->data()->mDVDImage.isNull());
5789
5790 Guid id;
5791 rc = mDVDDrive->data()->mDVDImage->COMGETTER(Id) (id.asOutParam());
5792 Assert (!id.isEmpty());
5793
5794 CFGNODE imageNode = 0;
5795 CFGLDRCreateChildNode (dvdNode, "Image", &imageNode);
5796 CFGLDRSetUUID (imageNode, "uuid", id.ptr());
5797 CFGLDRReleaseNode (imageNode);
5798 break;
5799 }
5800 case DriveState_HostDriveCaptured:
5801 {
5802 Assert (!mDVDDrive->data()->mHostDrive.isNull());
5803
5804 Bstr name;
5805 rc = mDVDDrive->data()->mHostDrive->COMGETTER(Name) (name.asOutParam());
5806 Assert (!name.isEmpty());
5807
5808 CFGNODE hostDriveNode = 0;
5809 CFGLDRCreateChildNode (dvdNode, "HostDrive", &hostDriveNode);
5810 CFGLDRSetBSTR (hostDriveNode, "src", name);
5811 CFGLDRReleaseNode (hostDriveNode);
5812 break;
5813 }
5814 case DriveState_NotMounted:
5815 /* do nothing, i.e.leave the DVD drive node empty */
5816 break;
5817 default:
5818 ComAssertMsgFailedBreak (("Invalid DVD drive state: %d\n",
5819 mDVDDrive->data()->mDriveState),
5820 rc = E_FAIL);
5821 }
5822
5823 CFGLDRReleaseNode (dvdNode);
5824 }
5825 while (0);
5826
5827 if (FAILED (rc))
5828 return rc;
5829
5830 /* Flooppy drive (required) */
5831 /// @todo (dmik) move the code to DVDDrive
5832 do
5833 {
5834 CFGNODE floppyNode = 0;
5835 CFGLDRCreateChildNode (aNode, "FloppyDrive", &floppyNode);
5836
5837 BOOL fFloppyEnabled;
5838 rc = mFloppyDrive->COMGETTER(Enabled)(&fFloppyEnabled);
5839 CFGLDRSetBool (floppyNode, "enabled", !!fFloppyEnabled);
5840
5841 switch (mFloppyDrive->data()->mDriveState)
5842 {
5843 case DriveState_ImageMounted:
5844 {
5845 Assert (!mFloppyDrive->data()->mFloppyImage.isNull());
5846
5847 Guid id;
5848 rc = mFloppyDrive->data()->mFloppyImage->COMGETTER(Id) (id.asOutParam());
5849 Assert (!id.isEmpty());
5850
5851 CFGNODE imageNode = 0;
5852 CFGLDRCreateChildNode (floppyNode, "Image", &imageNode);
5853 CFGLDRSetUUID (imageNode, "uuid", id.ptr());
5854 CFGLDRReleaseNode (imageNode);
5855 break;
5856 }
5857 case DriveState_HostDriveCaptured:
5858 {
5859 Assert (!mFloppyDrive->data()->mHostDrive.isNull());
5860
5861 Bstr name;
5862 rc = mFloppyDrive->data()->mHostDrive->COMGETTER(Name) (name.asOutParam());
5863 Assert (!name.isEmpty());
5864
5865 CFGNODE hostDriveNode = 0;
5866 CFGLDRCreateChildNode (floppyNode, "HostDrive", &hostDriveNode);
5867 CFGLDRSetBSTR (hostDriveNode, "src", name);
5868 CFGLDRReleaseNode (hostDriveNode);
5869 break;
5870 }
5871 case DriveState_NotMounted:
5872 /* do nothing, i.e.leave the Floppy drive node empty */
5873 break;
5874 default:
5875 ComAssertMsgFailedBreak (("Invalid Floppy drive state: %d\n",
5876 mFloppyDrive->data()->mDriveState),
5877 rc = E_FAIL);
5878 }
5879
5880 CFGLDRReleaseNode (floppyNode);
5881 }
5882 while (0);
5883
5884 if (FAILED (rc))
5885 return rc;
5886
5887
5888 /* USB Controller (required) */
5889 rc = mUSBController->saveSettings (aNode);
5890 if (FAILED (rc))
5891 return rc;
5892
5893 /* Network adapters (required) */
5894 do
5895 {
5896 CFGNODE nwNode = 0;
5897 CFGLDRCreateChildNode (aNode, "Network", &nwNode);
5898
5899 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
5900 {
5901 CFGNODE networkAdapterNode = 0;
5902 CFGLDRAppendChildNode (nwNode, "Adapter", &networkAdapterNode);
5903
5904 CFGLDRSetUInt32 (networkAdapterNode, "slot", slot);
5905 CFGLDRSetBool (networkAdapterNode, "enabled",
5906 !!mNetworkAdapters [slot]->data()->mEnabled);
5907 CFGLDRSetBSTR (networkAdapterNode, "MACAddress",
5908 mNetworkAdapters [slot]->data()->mMACAddress);
5909 CFGLDRSetBool (networkAdapterNode, "cable",
5910 !!mNetworkAdapters [slot]->data()->mCableConnected);
5911
5912 if (mNetworkAdapters [slot]->data()->mTraceEnabled)
5913 CFGLDRSetBool (networkAdapterNode, "trace", true);
5914
5915 CFGLDRSetBSTR (networkAdapterNode, "tracefile",
5916 mNetworkAdapters [slot]->data()->mTraceFile);
5917
5918 switch (mNetworkAdapters [slot]->data()->mAdapterType)
5919 {
5920 case NetworkAdapterType_NetworkAdapterAm79C970A:
5921 CFGLDRSetString (networkAdapterNode, "type", "Am79C970A");
5922 break;
5923 case NetworkAdapterType_NetworkAdapterAm79C973:
5924 CFGLDRSetString (networkAdapterNode, "type", "Am79C973");
5925 break;
5926 default:
5927 ComAssertMsgFailedBreak (("Invalid network adapter type: %d\n",
5928 mNetworkAdapters [slot]->data()->mAdapterType),
5929 rc = E_FAIL);
5930 }
5931
5932 CFGNODE attachmentNode = 0;
5933 switch (mNetworkAdapters [slot]->data()->mAttachmentType)
5934 {
5935 case NetworkAttachmentType_NoNetworkAttachment:
5936 {
5937 /* do nothing -- empty content */
5938 break;
5939 }
5940 case NetworkAttachmentType_NATNetworkAttachment:
5941 {
5942 CFGLDRAppendChildNode (networkAdapterNode, "NAT", &attachmentNode);
5943 break;
5944 }
5945 case NetworkAttachmentType_HostInterfaceNetworkAttachment:
5946 {
5947 CFGLDRAppendChildNode (networkAdapterNode, "HostInterface", &attachmentNode);
5948 const Bstr &name = mNetworkAdapters [slot]->data()->mHostInterface;
5949#ifdef __WIN__
5950 Assert (!name.isNull());
5951#endif
5952#ifdef VBOX_WITH_UNIXY_TAP_NETWORKING
5953 if (!name.isEmpty())
5954#endif
5955 CFGLDRSetBSTR (attachmentNode, "name", name);
5956#ifdef VBOX_WITH_UNIXY_TAP_NETWORKING
5957 const Bstr &tapSetupApp =
5958 mNetworkAdapters [slot]->data()->mTAPSetupApplication;
5959 if (!tapSetupApp.isEmpty())
5960 CFGLDRSetBSTR (attachmentNode, "TAPSetup", tapSetupApp);
5961 const Bstr &tapTerminateApp =
5962 mNetworkAdapters [slot]->data()->mTAPTerminateApplication;
5963 if (!tapTerminateApp.isEmpty())
5964 CFGLDRSetBSTR (attachmentNode, "TAPTerminate", tapTerminateApp);
5965#endif /* VBOX_WITH_UNIXY_TAP_NETWORKING */
5966 break;
5967 }
5968 case NetworkAttachmentType_InternalNetworkAttachment:
5969 {
5970 CFGLDRAppendChildNode (networkAdapterNode, "InternalNetwork", &attachmentNode);
5971 const Bstr &name = mNetworkAdapters[slot]->data()->mInternalNetwork;
5972 Assert(!name.isNull());
5973 CFGLDRSetBSTR (attachmentNode, "name", name);
5974 break;
5975 }
5976 default:
5977 {
5978 ComAssertFailedBreak (rc = E_FAIL);
5979 break;
5980 }
5981 }
5982 if (attachmentNode)
5983 CFGLDRReleaseNode (attachmentNode);
5984
5985 CFGLDRReleaseNode (networkAdapterNode);
5986 }
5987
5988 CFGLDRReleaseNode (nwNode);
5989 }
5990 while (0);
5991
5992 if (FAILED (rc))
5993 return rc;
5994
5995 /* Audio adapter */
5996 do
5997 {
5998 CFGNODE adapterNode = 0;
5999 CFGLDRCreateChildNode (aNode, "AudioAdapter", &adapterNode);
6000
6001 switch (mAudioAdapter->data()->mAudioDriver)
6002 {
6003 case AudioDriverType_NullAudioDriver:
6004 {
6005 CFGLDRSetString (adapterNode, "driver", "null");
6006 break;
6007 }
6008#ifdef __WIN__
6009 case AudioDriverType_WINMMAudioDriver:
6010#ifdef VBOX_WITH_WINMM
6011 {
6012 CFGLDRSetString (adapterNode, "driver", "winmm");
6013 break;
6014 }
6015#endif
6016 case AudioDriverType_DSOUNDAudioDriver:
6017 {
6018 CFGLDRSetString (adapterNode, "driver", "dsound");
6019 break;
6020 }
6021#endif /* __WIN__ */
6022#ifdef __LINUX__
6023 case AudioDriverType_ALSAAudioDriver:
6024#ifdef VBOX_WITH_ALSA
6025 {
6026 CFGLDRSetString (adapterNode, "driver", "alsa");
6027 break;
6028 }
6029#endif
6030 case AudioDriverType_OSSAudioDriver:
6031 {
6032 CFGLDRSetString (adapterNode, "driver", "oss");
6033 break;
6034 }
6035#endif /* __LINUX__ */
6036#ifdef __DARWIN__
6037 case AudioDriverType_CoreAudioDriver:
6038 {
6039 CFGLDRSetString (adapterNode, "driver", "coreaudio");
6040 break;
6041 }
6042#endif
6043 default:
6044 ComAssertMsgFailedBreak (("Wrong audio driver type! driver = %d\n",
6045 mAudioAdapter->data()->mAudioDriver),
6046 rc = E_FAIL);
6047 }
6048
6049 CFGLDRSetBool (adapterNode, "enabled", !!mAudioAdapter->data()->mEnabled);
6050
6051 CFGLDRReleaseNode (adapterNode);
6052 }
6053 while (0);
6054
6055 if (FAILED (rc))
6056 return rc;
6057
6058 /* Shared folders */
6059 do
6060 {
6061 CFGNODE sharedFoldersNode = 0;
6062 CFGLDRCreateChildNode (aNode, "SharedFolders", &sharedFoldersNode);
6063
6064 for (HWData::SharedFolderList::const_iterator it = mHWData->mSharedFolders.begin();
6065 it != mHWData->mSharedFolders.end();
6066 ++ it)
6067 {
6068 ComObjPtr <SharedFolder> folder = *it;
6069
6070 CFGNODE folderNode = 0;
6071 CFGLDRAppendChildNode (sharedFoldersNode, "SharedFolder", &folderNode);
6072
6073 /* all are mandatory */
6074 CFGLDRSetBSTR (folderNode, "name", folder->name());
6075 CFGLDRSetBSTR (folderNode, "hostPath", folder->hostPath());
6076
6077 CFGLDRReleaseNode (folderNode);
6078 }
6079
6080 CFGLDRReleaseNode (sharedFoldersNode);
6081 }
6082 while (0);
6083
6084 /* Clipboard */
6085 {
6086 CFGNODE clipNode = 0;
6087 CFGLDRCreateChildNode (aNode, "Clipboard", &clipNode);
6088
6089 const char *mode = "Disabled";
6090 switch (mHWData->mClipboardMode)
6091 {
6092 case ClipboardMode_ClipDisabled:
6093 /* already assigned */
6094 break;
6095 case ClipboardMode_ClipHostToGuest:
6096 mode = "HostToGuest";
6097 break;
6098 case ClipboardMode_ClipGuestToHost:
6099 mode = "GuestToHost";
6100 break;
6101 case ClipboardMode_ClipBidirectional:
6102 mode = "Bidirectional";
6103 break;
6104 default:
6105 AssertMsgFailed (("Clipboard mode %d is invalid",
6106 mHWData->mClipboardMode));
6107 break;
6108 }
6109 CFGLDRSetString (clipNode, "mode", mode);
6110
6111 CFGLDRReleaseNode (clipNode);
6112 }
6113
6114 return rc;
6115}
6116
6117/**
6118 * Saves the hard disk confguration.
6119 * It is assumed that the given node is empty.
6120 *
6121 * @param aNode <HardDiskAttachments> node to save the hard disk confguration to
6122 */
6123HRESULT Machine::saveHardDisks (CFGNODE aNode)
6124{
6125 AssertReturn (aNode, E_INVALIDARG);
6126
6127 HRESULT rc = S_OK;
6128
6129 for (HDData::HDAttachmentList::const_iterator it = mHDData->mHDAttachments.begin();
6130 it != mHDData->mHDAttachments.end() && SUCCEEDED (rc);
6131 ++ it)
6132 {
6133 ComObjPtr <HardDiskAttachment> att = *it;
6134
6135 CFGNODE hdNode = 0;
6136 CFGLDRAppendChildNode (aNode, "HardDiskAttachment", &hdNode);
6137
6138 do
6139 {
6140 const char *bus = NULL;
6141 switch (att->controller())
6142 {
6143 case DiskControllerType_IDE0Controller: bus = "ide0"; break;
6144 case DiskControllerType_IDE1Controller: bus = "ide1"; break;
6145 default:
6146 ComAssertFailedBreak (rc = E_FAIL);
6147 }
6148 if (FAILED (rc))
6149 break;
6150
6151 const char *dev = NULL;
6152 switch (att->deviceNumber())
6153 {
6154 case 0: dev = "master"; break;
6155 case 1: dev = "slave"; break;
6156 default:
6157 ComAssertFailedBreak (rc = E_FAIL);
6158 }
6159 if (FAILED (rc))
6160 break;
6161
6162 CFGLDRSetUUID (hdNode, "hardDisk", att->hardDisk()->id());
6163 CFGLDRSetString (hdNode, "bus", bus);
6164 CFGLDRSetString (hdNode, "device", dev);
6165 }
6166 while (0);
6167
6168 CFGLDRReleaseNode (hdNode);
6169 }
6170
6171 return rc;
6172}
6173
6174/**
6175 * Saves machine state settings as defined by aFlags
6176 * (SaveSTS_* values).
6177 *
6178 * @param aFlags a combination of SaveSTS_* flags
6179 *
6180 * @note Locks objects!
6181 */
6182HRESULT Machine::saveStateSettings (int aFlags)
6183{
6184 if (aFlags == 0)
6185 return S_OK;
6186
6187 AutoCaller autoCaller (this);
6188 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
6189
6190 AutoLock alock (this);
6191
6192 /* load the config file */
6193 CFGHANDLE configLoader = 0;
6194 HRESULT rc = openConfigLoader (&configLoader);
6195 if (FAILED (rc))
6196 return rc;
6197
6198 CFGNODE machineNode = 0;
6199 CFGLDRGetNode (configLoader, "VirtualBox/Machine", 0, &machineNode);
6200
6201 do
6202 {
6203 ComAssertBreak (machineNode, rc = E_FAIL);
6204
6205 if (aFlags & SaveSTS_CurStateModified)
6206 {
6207 if (!mData->mCurrentStateModified)
6208 CFGLDRSetBool (machineNode, "currentStateModified", false);
6209 else
6210 CFGLDRDeleteAttribute (machineNode, "currentStateModified");
6211 }
6212
6213 if (aFlags & SaveSTS_StateFilePath)
6214 {
6215 if (mSSData->mStateFilePath)
6216 CFGLDRSetBSTR (machineNode, "stateFile", mSSData->mStateFilePath);
6217 else
6218 CFGLDRDeleteAttribute (machineNode, "stateFile");
6219 }
6220
6221 if (aFlags & SaveSTS_StateTimeStamp)
6222 {
6223 Assert (mData->mMachineState != MachineState_Aborted ||
6224 mSSData->mStateFilePath.isNull());
6225
6226 CFGLDRSetDateTime (machineNode, "lastStateChange",
6227 mData->mLastStateChange);
6228
6229 // set the aborted attribute when appropriate
6230 if (mData->mMachineState == MachineState_Aborted)
6231 CFGLDRSetBool (machineNode, "aborted", true);
6232 else
6233 CFGLDRDeleteAttribute (machineNode, "aborted");
6234 }
6235 }
6236 while (0);
6237
6238 if (machineNode)
6239 CFGLDRReleaseNode (machineNode);
6240
6241 if (SUCCEEDED (rc))
6242 rc = closeConfigLoader (configLoader, true /* aSaveBeforeClose */);
6243 else
6244 closeConfigLoader (configLoader, false /* aSaveBeforeClose */);
6245
6246 return rc;
6247}
6248
6249/**
6250 * Cleans up all differencing hard disks based on immutable hard disks.
6251 *
6252 * @note Locks objects!
6253 */
6254HRESULT Machine::wipeOutImmutableDiffs()
6255{
6256 AutoCaller autoCaller (this);
6257 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
6258
6259 AutoReaderLock alock (this);
6260
6261 AssertReturn (mData->mMachineState == MachineState_PoweredOff ||
6262 mData->mMachineState == MachineState_Aborted, E_FAIL);
6263
6264 for (HDData::HDAttachmentList::const_iterator it = mHDData->mHDAttachments.begin();
6265 it != mHDData->mHDAttachments.end();
6266 ++ it)
6267 {
6268 ComObjPtr <HardDisk> hd = (*it)->hardDisk();
6269 AutoLock hdLock (hd);
6270
6271 if(hd->isParentImmutable())
6272 {
6273 /// @todo (dmik) no error handling for now
6274 // (need async error reporting for this)
6275 hd->asVDI()->wipeOutImage();
6276 }
6277 }
6278
6279 return S_OK;
6280}
6281
6282/**
6283 * Fixes up lazy hard disk attachments by creating or deleting differencing
6284 * hard disks when machine settings are being committed.
6285 * Must be called only from #commit().
6286 *
6287 * @note Locks objects!
6288 */
6289HRESULT Machine::fixupHardDisks (bool aCommit)
6290{
6291 AutoCaller autoCaller (this);
6292 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
6293
6294 AutoLock alock (this);
6295
6296 /* no attac/detach operations -- nothing to do */
6297 if (!mHDData.isBackedUp())
6298 {
6299 mHDData->mHDAttachmentsChanged = false;
6300 return S_OK;
6301 }
6302
6303 AssertReturn (mData->mRegistered, E_FAIL);
6304
6305 if (aCommit)
6306 {
6307 /*
6308 * changes are being committed,
6309 * perform actual diff image creation, deletion etc.
6310 */
6311
6312 /* take a copy of backed up attachments (will modify it) */
6313 HDData::HDAttachmentList backedUp = mHDData.backedUpData()->mHDAttachments;
6314 /* list of new diffs created */
6315 std::list <ComObjPtr <HardDisk> > newDiffs;
6316
6317 HRESULT rc = S_OK;
6318
6319 /* go through current attachments */
6320 for (HDData::HDAttachmentList::const_iterator
6321 it = mHDData->mHDAttachments.begin();
6322 it != mHDData->mHDAttachments.end();
6323 ++ it)
6324 {
6325 ComObjPtr <HardDiskAttachment> hda = *it;
6326 ComObjPtr <HardDisk> hd = hda->hardDisk();
6327 AutoLock hdLock (hd);
6328
6329 if (!hda->isDirty())
6330 {
6331 /*
6332 * not dirty, therefore was either attached before backing up
6333 * or doesn't need any fixup (already fixed up); try to locate
6334 * this hard disk among backed up attachments and remove from
6335 * there to prevent it from being deassociated/deleted
6336 */
6337 HDData::HDAttachmentList::iterator oldIt;
6338 for (oldIt = backedUp.begin(); oldIt != backedUp.end(); ++ oldIt)
6339 if ((*oldIt)->hardDisk().equalsTo (hd))
6340 break;
6341 if (oldIt != backedUp.end())
6342 {
6343 /* remove from there */
6344 backedUp.erase (oldIt);
6345 Log3 (("FC: %ls found in old\n", hd->toString().raw()));
6346 }
6347 }
6348 else
6349 {
6350 /* dirty, determine what to do */
6351
6352 bool needDiff = false;
6353 bool searchAmongSnapshots = false;
6354
6355 switch (hd->type())
6356 {
6357 case HardDiskType_ImmutableHardDisk:
6358 {
6359 /* decrease readers increased in AttachHardDisk() */
6360 hd->releaseReader();
6361 Log3 (("FC: %ls released\n", hd->toString().raw()));
6362 /* indicate we need a diff (indirect attachment) */
6363 needDiff = true;
6364 break;
6365 }
6366 case HardDiskType_WritethroughHardDisk:
6367 {
6368 /* reset the dirty flag */
6369 hda->updateHardDisk (hd, false /* aDirty */);
6370 Log3 (("FC: %ls updated\n", hd->toString().raw()));
6371 break;
6372 }
6373 case HardDiskType_NormalHardDisk:
6374 {
6375 if (hd->snapshotId().isEmpty())
6376 {
6377 /* reset the dirty flag */
6378 hda->updateHardDisk (hd, false /* aDirty */);
6379 Log3 (("FC: %ls updated\n", hd->toString().raw()));
6380 }
6381 else
6382 {
6383 /* decrease readers increased in AttachHardDisk() */
6384 hd->releaseReader();
6385 Log3 (("FC: %ls released\n", hd->toString().raw()));
6386 /* indicate we need a diff (indirect attachment) */
6387 needDiff = true;
6388 /* search for the most recent base among snapshots */
6389 searchAmongSnapshots = true;
6390 }
6391 break;
6392 }
6393 }
6394
6395 if (!needDiff)
6396 continue;
6397
6398 bool createDiff = false;
6399
6400 /*
6401 * see whether any previously attached hard disk has the
6402 * the currently attached one (Normal or Independent) as
6403 * the root
6404 */
6405
6406 HDData::HDAttachmentList::iterator foundIt = backedUp.end();
6407
6408 for (HDData::HDAttachmentList::iterator it = backedUp.begin();
6409 it != backedUp.end();
6410 ++ it)
6411 {
6412 if ((*it)->hardDisk()->root().equalsTo (hd))
6413 {
6414 /*
6415 * matched dev and ctl (i.e. attached to the same place)
6416 * will win and immediately stop the search; otherwise
6417 * the first attachment that matched the hd only will
6418 * be used
6419 */
6420 if ((*it)->deviceNumber() == hda->deviceNumber() &&
6421 (*it)->controller() == hda->controller())
6422 {
6423 foundIt = it;
6424 break;
6425 }
6426 else
6427 if (foundIt == backedUp.end())
6428 {
6429 /*
6430 * not an exact match; ensure there is no exact match
6431 * among other current attachments referring the same
6432 * root (to prevent this attachmend from reusing the
6433 * hard disk of the other attachment that will later
6434 * give the exact match or already gave it before)
6435 */
6436 bool canReuse = true;
6437 for (HDData::HDAttachmentList::const_iterator
6438 it2 = mHDData->mHDAttachments.begin();
6439 it2 != mHDData->mHDAttachments.end();
6440 ++ it2)
6441 {
6442 if ((*it2)->deviceNumber() == (*it)->deviceNumber() &&
6443 (*it2)->controller() == (*it)->controller() &&
6444 (*it2)->hardDisk()->root().equalsTo (hd))
6445 {
6446 /*
6447 * the exact match, either non-dirty or dirty
6448 * one refers the same root: in both cases
6449 * we cannot reuse the hard disk, so break
6450 */
6451 canReuse = false;
6452 break;
6453 }
6454 }
6455
6456 if (canReuse)
6457 foundIt = it;
6458 }
6459 }
6460 }
6461
6462 if (foundIt != backedUp.end())
6463 {
6464 /* found either one or another, reuse the diff */
6465 hda->updateHardDisk ((*foundIt)->hardDisk(),
6466 false /* aDirty */);
6467 Log3 (("FC: %ls reused as %ls\n", hd->toString().raw(),
6468 (*foundIt)->hardDisk()->toString().raw()));
6469 /* remove from there */
6470 backedUp.erase (foundIt);
6471 }
6472 else
6473 {
6474 /* was not attached, need a diff */
6475 createDiff = true;
6476 }
6477
6478 if (!createDiff)
6479 continue;
6480
6481 ComObjPtr <HardDisk> baseHd = hd;
6482
6483 if (searchAmongSnapshots)
6484 {
6485 /*
6486 * find the most recent diff based on the currently
6487 * attached root (Normal hard disk) among snapshots
6488 */
6489
6490 ComObjPtr <Snapshot> snap = mData->mCurrentSnapshot;
6491
6492 while (snap)
6493 {
6494 AutoLock snapLock (snap);
6495
6496 const HDData::HDAttachmentList &snapAtts =
6497 snap->data().mMachine->hdData()->mHDAttachments;
6498
6499 HDData::HDAttachmentList::const_iterator foundIt = snapAtts.end();
6500
6501 for (HDData::HDAttachmentList::const_iterator
6502 it = snapAtts.begin(); it != snapAtts.end(); ++ it)
6503 {
6504 if ((*it)->hardDisk()->root().equalsTo (hd))
6505 {
6506 /*
6507 * matched dev and ctl (i.e. attached to the same place)
6508 * will win and immediately stop the search; otherwise
6509 * the first attachment that matched the hd only will
6510 * be used
6511 */
6512 if ((*it)->deviceNumber() == hda->deviceNumber() &&
6513 (*it)->controller() == hda->controller())
6514 {
6515 foundIt = it;
6516 break;
6517 }
6518 else
6519 if (foundIt == snapAtts.end())
6520 foundIt = it;
6521 }
6522 }
6523
6524 if (foundIt != snapAtts.end())
6525 {
6526 /* the most recent diff has been found, use as a base */
6527 baseHd = (*foundIt)->hardDisk();
6528 Log3 (("FC: %ls: recent found %ls\n",
6529 hd->toString().raw(), baseHd->toString().raw()));
6530 break;
6531 }
6532
6533 snap = snap->parent();
6534 }
6535 }
6536
6537 /* create a new diff for the hard disk being indirectly attached */
6538
6539 AutoLock baseHdLock (baseHd);
6540 baseHd->addReader();
6541
6542 ComObjPtr <HVirtualDiskImage> vdi;
6543 rc = baseHd->createDiffHardDisk (mUserData->mSnapshotFolderFull,
6544 mData->mUuid, vdi, NULL);
6545 baseHd->releaseReader();
6546 CheckComRCBreakRC (rc);
6547
6548 newDiffs.push_back (ComObjPtr <HardDisk> (vdi));
6549
6550 /* update the attachment and reset the dirty flag */
6551 hda->updateHardDisk (ComObjPtr <HardDisk> (vdi),
6552 false /* aDirty */);
6553 Log3 (("FC: %ls: diff created %ls\n",
6554 baseHd->toString().raw(), vdi->toString().raw()));
6555 }
6556 }
6557
6558 if (FAILED (rc))
6559 {
6560 /* delete diffs we created */
6561 for (std::list <ComObjPtr <HardDisk> >::const_iterator
6562 it = newDiffs.begin(); it != newDiffs.end(); ++ it)
6563 {
6564 /*
6565 * unregisterDiffHardDisk() is supposed to delete and uninit
6566 * the differencing hard disk
6567 */
6568 mParent->unregisterDiffHardDisk (*it);
6569 /* too bad if we fail here, but nothing to do, just continue */
6570 }
6571
6572 /* the best is to rollback the changes... */
6573 mHDData.rollback();
6574 mHDData->mHDAttachmentsChanged = false;
6575 Log3 (("FC: ROLLED BACK\n"));
6576 return rc;
6577 }
6578
6579 /*
6580 * go through the rest of old attachments and delete diffs
6581 * or deassociate hard disks from machines (they will become detached)
6582 */
6583 for (HDData::HDAttachmentList::iterator
6584 it = backedUp.begin(); it != backedUp.end(); ++ it)
6585 {
6586 ComObjPtr <HardDiskAttachment> hda = *it;
6587 ComObjPtr <HardDisk> hd = hda->hardDisk();
6588 AutoLock hdLock (hd);
6589
6590 if (hd->isDifferencing())
6591 {
6592 /*
6593 * unregisterDiffHardDisk() is supposed to delete and uninit
6594 * the differencing hard disk
6595 */
6596 Log3 (("FC: %ls diff deleted\n", hd->toString().raw()));
6597 rc = mParent->unregisterDiffHardDisk (hd);
6598 /*
6599 * too bad if we fail here, but nothing to do, just continue
6600 * (the last rc will be returned to the caller though)
6601 */
6602 }
6603 else
6604 {
6605 /* deassociate from this machine */
6606 Log3 (("FC: %ls deassociated\n", hd->toString().raw()));
6607 hd->setMachineId (Guid());
6608 }
6609 }
6610
6611 /* commit all the changes */
6612 mHDData->mHDAttachmentsChanged = mHDData.hasActualChanges();
6613 mHDData.commit();
6614 Log3 (("FC: COMMITTED\n"));
6615
6616 return rc;
6617 }
6618
6619 /*
6620 * changes are being rolled back,
6621 * go trhough all current attachments and fix up dirty ones
6622 * the way it is done in DetachHardDisk()
6623 */
6624
6625 for (HDData::HDAttachmentList::iterator it = mHDData->mHDAttachments.begin();
6626 it != mHDData->mHDAttachments.end();
6627 ++ it)
6628 {
6629 ComObjPtr <HardDiskAttachment> hda = *it;
6630 ComObjPtr <HardDisk> hd = hda->hardDisk();
6631 AutoLock hdLock (hd);
6632
6633 if (hda->isDirty())
6634 {
6635 switch (hd->type())
6636 {
6637 case HardDiskType_ImmutableHardDisk:
6638 {
6639 /* decrease readers increased in AttachHardDisk() */
6640 hd->releaseReader();
6641 Log3 (("FR: %ls released\n", hd->toString().raw()));
6642 break;
6643 }
6644 case HardDiskType_WritethroughHardDisk:
6645 {
6646 /* deassociate from this machine */
6647 hd->setMachineId (Guid());
6648 Log3 (("FR: %ls deassociated\n", hd->toString().raw()));
6649 break;
6650 }
6651 case HardDiskType_NormalHardDisk:
6652 {
6653 if (hd->snapshotId().isEmpty())
6654 {
6655 /* deassociate from this machine */
6656 hd->setMachineId (Guid());
6657 Log3 (("FR: %ls deassociated\n", hd->toString().raw()));
6658 }
6659 else
6660 {
6661 /* decrease readers increased in AttachHardDisk() */
6662 hd->releaseReader();
6663 Log3 (("FR: %ls released\n", hd->toString().raw()));
6664 }
6665
6666 break;
6667 }
6668 }
6669 }
6670 }
6671
6672 /* rollback all the changes */
6673 mHDData.rollback();
6674 Log3 (("FR: ROLLED BACK\n"));
6675
6676 return S_OK;
6677}
6678
6679/**
6680 * Creates differencing hard disks for all normal hard disks
6681 * and replaces attachments to refer to created disks.
6682 * Used when taking a snapshot or when discarding the current state.
6683 *
6684 * @param aSnapshotId ID of the snapshot being taken
6685 * or NULL if the current state is being discarded
6686 * @param aFolder folder where to create diff. hard disks
6687 * @param aProgress progress object to run (must contain at least as
6688 * many operations left as the number of VDIs attached)
6689 * @param aOnline whether the machine is online (i.e., when the EMT
6690 * thread is paused, OR when current hard disks are
6691 * marked as busy for some other reason)
6692 *
6693 * @note
6694 * The progress object is not marked as completed, neither on success
6695 * nor on failure. This is a responsibility of the caller.
6696 *
6697 * @note Locks mParent + this object for writing
6698 */
6699HRESULT Machine::createSnapshotDiffs (const Guid *aSnapshotId,
6700 const Bstr &aFolder,
6701 const ComObjPtr <Progress> &aProgress,
6702 bool aOnline)
6703{
6704 AssertReturn (!aFolder.isEmpty(), E_FAIL);
6705
6706 AutoCaller autoCaller (this);
6707 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
6708
6709 /* accessing mParent methods below needs mParent lock */
6710 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
6711
6712 HRESULT rc = S_OK;
6713
6714 // first pass: check accessibility before performing changes
6715 if (!aOnline)
6716 {
6717 for (HDData::HDAttachmentList::const_iterator it = mHDData->mHDAttachments.begin();
6718 it != mHDData->mHDAttachments.end();
6719 ++ it)
6720 {
6721 ComObjPtr <HardDiskAttachment> hda = *it;
6722 ComObjPtr <HardDisk> hd = hda->hardDisk();
6723 AutoLock hdLock (hd);
6724
6725 ComAssertMsgBreak (hd->type() == HardDiskType_NormalHardDisk,
6726 ("Invalid hard disk type %d\n", hd->type()),
6727 rc = E_FAIL);
6728
6729 ComAssertMsgBreak (!hd->isParentImmutable() ||
6730 hd->storageType() == HardDiskStorageType_VirtualDiskImage,
6731 ("Invalid hard disk storage type %d\n", hd->storageType()),
6732 rc = E_FAIL);
6733
6734 Bstr accessError;
6735 rc = hd->getAccessible (accessError);
6736 CheckComRCBreakRC (rc);
6737
6738 if (!accessError.isNull())
6739 {
6740 rc = setError (E_FAIL,
6741 tr ("Hard disk '%ls' is not accessible (%ls)"),
6742 hd->toString().raw(), accessError.raw());
6743 break;
6744 }
6745 }
6746 CheckComRCReturnRC (rc);
6747 }
6748
6749 HDData::HDAttachmentList attachments;
6750
6751 // second pass: perform changes
6752 for (HDData::HDAttachmentList::const_iterator it = mHDData->mHDAttachments.begin();
6753 it != mHDData->mHDAttachments.end();
6754 ++ it)
6755 {
6756 ComObjPtr <HardDiskAttachment> hda = *it;
6757 ComObjPtr <HardDisk> hd = hda->hardDisk();
6758 AutoLock hdLock (hd);
6759
6760 ComObjPtr <HardDisk> parent = hd->parent();
6761 AutoLock parentHdLock (parent);
6762
6763 ComObjPtr <HardDisk> newHd;
6764
6765 // clear busy flag if the VM is online
6766 if (aOnline)
6767 hd->clearBusy();
6768 // increase readers
6769 hd->addReader();
6770
6771 if (hd->isParentImmutable())
6772 {
6773 aProgress->advanceOperation (Bstr (Utf8StrFmt (
6774 tr ("Preserving immutable hard disk '%ls'"),
6775 parent->toString (true /* aShort */).raw())));
6776
6777 parentHdLock.unlock();
6778 alock.leave();
6779
6780 // create a copy of the independent diff
6781 ComObjPtr <HVirtualDiskImage> vdi;
6782 rc = hd->asVDI()->cloneDiffImage (aFolder, mData->mUuid, vdi,
6783 aProgress);
6784 newHd = vdi;
6785
6786 alock.enter();
6787 parentHdLock.lock();
6788
6789 // decrease readers (hd is no more used for reading in any case)
6790 hd->releaseReader();
6791 }
6792 else
6793 {
6794 // checked in the first pass
6795 Assert (hd->type() == HardDiskType_NormalHardDisk);
6796
6797 aProgress->advanceOperation (Bstr (Utf8StrFmt (
6798 tr ("Creating a differencing hard disk for '%ls'"),
6799 hd->root()->toString (true /* aShort */).raw())));
6800
6801 parentHdLock.unlock();
6802 alock.leave();
6803
6804 // create a new diff for the image being attached
6805 ComObjPtr <HVirtualDiskImage> vdi;
6806 rc = hd->createDiffHardDisk (aFolder, mData->mUuid, vdi, aProgress);
6807 newHd = vdi;
6808
6809 alock.enter();
6810 parentHdLock.lock();
6811
6812 if (SUCCEEDED (rc))
6813 {
6814 // if online, hd must keep a reader referece
6815 if (!aOnline)
6816 hd->releaseReader();
6817 }
6818 else
6819 {
6820 // decrease readers
6821 hd->releaseReader();
6822 }
6823 }
6824
6825 if (SUCCEEDED (rc))
6826 {
6827 ComObjPtr <HardDiskAttachment> newHda;
6828 newHda.createObject();
6829 rc = newHda->init (newHd, hda->controller(), hda->deviceNumber(),
6830 false /* aDirty */);
6831
6832 if (SUCCEEDED (rc))
6833 {
6834 // associate the snapshot id with the old hard disk
6835 if (hd->type() != HardDiskType_WritethroughHardDisk && aSnapshotId)
6836 hd->setSnapshotId (*aSnapshotId);
6837
6838 // add the new attachment
6839 attachments.push_back (newHda);
6840
6841 // if online, newHd must be marked as busy
6842 if (aOnline)
6843 newHd->setBusy();
6844 }
6845 }
6846
6847 if (FAILED (rc))
6848 {
6849 // set busy flag back if the VM is online
6850 if (aOnline)
6851 hd->setBusy();
6852 break;
6853 }
6854 }
6855
6856 if (SUCCEEDED (rc))
6857 {
6858 // replace the whole list of attachments with the new one
6859 mHDData->mHDAttachments = attachments;
6860 }
6861 else
6862 {
6863 // delete those diffs we've just created
6864 for (HDData::HDAttachmentList::const_iterator it = attachments.begin();
6865 it != attachments.end();
6866 ++ it)
6867 {
6868 ComObjPtr <HardDisk> hd = (*it)->hardDisk();
6869 AutoLock hdLock (hd);
6870 Assert (hd->children().size() == 0);
6871 Assert (hd->isDifferencing());
6872 // unregisterDiffHardDisk() is supposed to delete and uninit
6873 // the differencing hard disk
6874 mParent->unregisterDiffHardDisk (hd);
6875 }
6876 }
6877
6878 return rc;
6879}
6880
6881/**
6882 * Deletes differencing hard disks created by createSnapshotDiffs() in case
6883 * if snapshot creation was failed.
6884 *
6885 * @param aSnapshot failed snapshot
6886 *
6887 * @note Locks mParent + this object for writing.
6888 */
6889HRESULT Machine::deleteSnapshotDiffs (const ComObjPtr <Snapshot> &aSnapshot)
6890{
6891 AssertReturn (!aSnapshot.isNull(), E_FAIL);
6892
6893 AutoCaller autoCaller (this);
6894 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
6895
6896 /* accessing mParent methods below needs mParent lock */
6897 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
6898
6899 /* short cut: check whether attachments are all the same */
6900 if (mHDData->mHDAttachments == aSnapshot->data().mMachine->mHDData->mHDAttachments)
6901 return S_OK;
6902
6903 HRESULT rc = S_OK;
6904
6905 for (HDData::HDAttachmentList::const_iterator it = mHDData->mHDAttachments.begin();
6906 it != mHDData->mHDAttachments.end();
6907 ++ it)
6908 {
6909 ComObjPtr <HardDiskAttachment> hda = *it;
6910 ComObjPtr <HardDisk> hd = hda->hardDisk();
6911 AutoLock hdLock (hd);
6912
6913 ComObjPtr <HardDisk> parent = hd->parent();
6914 AutoLock parentHdLock (parent);
6915
6916 if (!parent || parent->snapshotId() != aSnapshot->data().mId)
6917 continue;
6918
6919 /* must not have children */
6920 ComAssertRet (hd->children().size() == 0, E_FAIL);
6921
6922 /* deassociate the old hard disk from the given snapshot's ID */
6923 parent->setSnapshotId (Guid());
6924
6925 /* unregisterDiffHardDisk() is supposed to delete and uninit
6926 * the differencing hard disk */
6927 rc = mParent->unregisterDiffHardDisk (hd);
6928 /* continue on error */
6929 }
6930
6931 /* restore the whole list of attachments from the failed snapshot */
6932 mHDData->mHDAttachments = aSnapshot->data().mMachine->mHDData->mHDAttachments;
6933
6934 return rc;
6935}
6936
6937/**
6938 * Helper to lock the machine configuration for write access.
6939 *
6940 * @return S_OK or E_FAIL and sets error info on failure
6941 *
6942 * @note Doesn't lock anything (must be called from this object's lock)
6943 */
6944HRESULT Machine::lockConfig()
6945{
6946 HRESULT rc = S_OK;
6947
6948 if (!isConfigLocked())
6949 {
6950 /* open the associated config file */
6951 int vrc = RTFileOpen (&mData->mHandleCfgFile,
6952 Utf8Str (mData->mConfigFileFull),
6953 RTFILE_O_READWRITE | RTFILE_O_OPEN |
6954 RTFILE_O_DENY_WRITE);
6955 if (VBOX_FAILURE (vrc))
6956 mData->mHandleCfgFile = NIL_RTFILE;
6957 }
6958
6959 LogFlowThisFunc (("mConfigFile={%ls}, mHandleCfgFile=%d, rc=%08X\n",
6960 mData->mConfigFileFull.raw(), mData->mHandleCfgFile, rc));
6961 return rc;
6962}
6963
6964/**
6965 * Helper to unlock the machine configuration from write access
6966 *
6967 * @return S_OK
6968 *
6969 * @note Doesn't lock anything.
6970 * @note Not thread safe (must be called from this object's lock).
6971 */
6972HRESULT Machine::unlockConfig()
6973{
6974 HRESULT rc = S_OK;
6975
6976 if (isConfigLocked())
6977 {
6978 RTFileFlush(mData->mHandleCfgFile);
6979 RTFileClose(mData->mHandleCfgFile);
6980 /** @todo flush the directory. */
6981 mData->mHandleCfgFile = NIL_RTFILE;
6982 }
6983
6984 LogFlowThisFunc (("\n"));
6985
6986 return rc;
6987}
6988
6989/**
6990 * Returns true if the settings file is located in the directory named exactly
6991 * as the machine. This will be true if the machine settings structure was
6992 * created by default in #openConfigLoader().
6993 *
6994 * @param aSettingsDir if not NULL, the full machine settings file directory
6995 * name will be assigned there.
6996 *
6997 * @note Doesn't lock anything.
6998 * @note Not thread safe (must be called from this object's lock).
6999 */
7000bool Machine::isInOwnDir (Utf8Str *aSettingsDir /* = NULL */)
7001{
7002 Utf8Str settingsDir = mData->mConfigFileFull;
7003 RTPathStripFilename (settingsDir.mutableRaw());
7004 char *dirName = RTPathFilename (settingsDir);
7005
7006 AssertReturn (dirName, false);
7007
7008 /* if we don't rename anything on name change, return false shorlty */
7009 if (!mUserData->mNameSync)
7010 return false;
7011
7012 if (aSettingsDir)
7013 *aSettingsDir = settingsDir;
7014
7015 return Bstr (dirName) == mUserData->mName;
7016}
7017
7018/**
7019 * @note Locks objects for reading!
7020 */
7021bool Machine::isModified()
7022{
7023 AutoCaller autoCaller (this);
7024 AssertComRCReturn (autoCaller.rc(), false);
7025
7026 AutoReaderLock alock (this);
7027
7028 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
7029 if (mNetworkAdapters [slot] && mNetworkAdapters [slot]->isModified())
7030 return true;
7031
7032 return
7033 mUserData.isBackedUp() ||
7034 mHWData.isBackedUp() ||
7035 mHDData.isBackedUp() ||
7036#ifdef VBOX_VRDP
7037 (mVRDPServer && mVRDPServer->isModified()) ||
7038#endif
7039 (mDVDDrive && mDVDDrive->isModified()) ||
7040 (mFloppyDrive && mFloppyDrive->isModified()) ||
7041 (mAudioAdapter && mAudioAdapter->isModified()) ||
7042 (mUSBController && mUSBController->isModified()) ||
7043 (mBIOSSettings && mBIOSSettings->isModified());
7044}
7045
7046/**
7047 * @note This method doesn't check (ignores) actual changes to mHDData.
7048 * Use mHDData.mHDAttachmentsChanged right after #commit() instead.
7049 *
7050 * @param aIgnoreUserData |true| to ignore changes to mUserData
7051 *
7052 * @note Locks objects for reading!
7053 */
7054bool Machine::isReallyModified (bool aIgnoreUserData /* = false */)
7055{
7056 AutoCaller autoCaller (this);
7057 AssertComRCReturn (autoCaller.rc(), false);
7058
7059 AutoReaderLock alock (this);
7060
7061 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
7062 if (mNetworkAdapters [slot] && mNetworkAdapters [slot]->isReallyModified())
7063 return true;
7064
7065 return
7066 (!aIgnoreUserData && mUserData.hasActualChanges()) ||
7067 mHWData.hasActualChanges() ||
7068 /* ignore mHDData */
7069 //mHDData.hasActualChanges() ||
7070#ifdef VBOX_VRDP
7071 (mVRDPServer && mVRDPServer->isReallyModified()) ||
7072#endif
7073 (mDVDDrive && mDVDDrive->isReallyModified()) ||
7074 (mFloppyDrive && mFloppyDrive->isReallyModified()) ||
7075 (mAudioAdapter && mAudioAdapter->isReallyModified()) ||
7076 (mUSBController && mUSBController->isReallyModified()) ||
7077 (mBIOSSettings && mBIOSSettings->isReallyModified());
7078}
7079
7080/**
7081 * Discards all changes to machine settings.
7082 *
7083 * @param aNotify whether to notify the direct session about changes or not
7084 *
7085 * @note Locks objects!
7086 */
7087void Machine::rollback (bool aNotify)
7088{
7089 AutoCaller autoCaller (this);
7090 AssertComRCReturn (autoCaller.rc(), (void) 0);
7091
7092 AutoLock alock (this);
7093
7094 mUserData.rollback();
7095
7096 mHWData.rollback();
7097
7098 if (mHDData.isBackedUp())
7099 fixupHardDisks (false /* aCommit */);
7100
7101 bool vrdpChanged = false, dvdChanged = false, floppyChanged = false,
7102 usbChanged = false;
7103 ComPtr <INetworkAdapter> networkAdapters [ELEMENTS (mNetworkAdapters)];
7104
7105 if (mBIOSSettings)
7106 mBIOSSettings->rollback();
7107
7108#ifdef VBOX_VRDP
7109 if (mVRDPServer)
7110 vrdpChanged = mVRDPServer->rollback();
7111#endif
7112
7113 if (mDVDDrive)
7114 dvdChanged = mDVDDrive->rollback();
7115
7116 if (mFloppyDrive)
7117 floppyChanged = mFloppyDrive->rollback();
7118
7119 if (mAudioAdapter)
7120 mAudioAdapter->rollback();
7121
7122 if (mUSBController)
7123 usbChanged = mUSBController->rollback();
7124
7125 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
7126 if (mNetworkAdapters [slot])
7127 if (mNetworkAdapters [slot]->rollback())
7128 networkAdapters [slot] = mNetworkAdapters [slot];
7129
7130 if (aNotify)
7131 {
7132 // inform the direct session about changes
7133
7134 ComObjPtr <Machine> that = this;
7135 alock.leave();
7136
7137 if (vrdpChanged)
7138 that->onVRDPServerChange();
7139 if (dvdChanged)
7140 that->onDVDDriveChange();
7141 if (floppyChanged)
7142 that->onFloppyDriveChange();
7143 if (usbChanged)
7144 that->onUSBControllerChange();
7145 for (ULONG slot = 0; slot < ELEMENTS (networkAdapters); slot ++)
7146 if (networkAdapters [slot])
7147 that->onNetworkAdapterChange (networkAdapters [slot]);
7148 }
7149}
7150
7151/**
7152 * Commits all the changes to machine settings.
7153 *
7154 * Note that when committing fails at some stage, it still continues
7155 * until the end. So, all data will either be actually committed or rolled
7156 * back (for failed cases) and the returned result code will describe the
7157 * first failure encountered. However, #isModified() will still return true
7158 * in case of failure, to indicade that settings in memory and on disk are
7159 * out of sync.
7160 *
7161 * @note Locks objects!
7162 */
7163HRESULT Machine::commit()
7164{
7165 AutoCaller autoCaller (this);
7166 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7167
7168 AutoLock alock (this);
7169
7170 HRESULT rc = S_OK;
7171
7172 /*
7173 * use safe commit to ensure Snapshot machines (that share mUserData)
7174 * will still refer to a valid memory location
7175 */
7176 mUserData.commitCopy();
7177
7178 mHWData.commit();
7179
7180 if (mHDData.isBackedUp())
7181 rc = fixupHardDisks (true /* aCommit */);
7182
7183 mBIOSSettings->commit();
7184#ifdef VBOX_VRDP
7185 mVRDPServer->commit();
7186#endif
7187 mDVDDrive->commit();
7188 mFloppyDrive->commit();
7189 mAudioAdapter->commit();
7190 mUSBController->commit();
7191
7192 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
7193 mNetworkAdapters [slot]->commit();
7194
7195 if (mType == IsSessionMachine)
7196 {
7197 /* attach new data to the primary machine and reshare it */
7198 mPeer->mUserData.attach (mUserData);
7199 mPeer->mHWData.attach (mHWData);
7200 mPeer->mHDData.attach (mHDData);
7201 }
7202
7203 if (FAILED (rc))
7204 {
7205 /*
7206 * backup arbitrary data item to cause #isModified() to still return
7207 * true in case of any error
7208 */
7209 mHWData.backup();
7210 }
7211
7212 return rc;
7213}
7214
7215/**
7216 * Copies all the hardware data from the given machine.
7217 *
7218 * @note
7219 * This method must be called from under this object's lock.
7220 * @note
7221 * This method doesn't call #commit(), so all data remains backed up
7222 * and unsaved.
7223 */
7224void Machine::copyFrom (Machine *aThat)
7225{
7226 AssertReturn (mType == IsMachine || mType == IsSessionMachine, (void) 0);
7227 AssertReturn (aThat->mType == IsSnapshotMachine, (void) 0);
7228
7229 mHWData.assignCopy (aThat->mHWData);
7230
7231 // create copies of all shared folders (mHWData after attiching a copy
7232 // contains just references to original objects)
7233 for (HWData::SharedFolderList::iterator it = mHWData->mSharedFolders.begin();
7234 it != mHWData->mSharedFolders.end();
7235 ++ it)
7236 {
7237 ComObjPtr <SharedFolder> folder;
7238 folder.createObject();
7239 HRESULT rc = folder->initCopy (machine(), *it);
7240 AssertComRC (rc);
7241 *it = folder;
7242 }
7243
7244 mBIOSSettings->copyFrom (aThat->mBIOSSettings);
7245#ifdef VBOX_VRDP
7246 mVRDPServer->copyFrom (aThat->mVRDPServer);
7247#endif
7248 mDVDDrive->copyFrom (aThat->mDVDDrive);
7249 mFloppyDrive->copyFrom (aThat->mFloppyDrive);
7250 mAudioAdapter->copyFrom (aThat->mAudioAdapter);
7251 mUSBController->copyFrom (aThat->mUSBController);
7252
7253 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
7254 mNetworkAdapters [slot]->copyFrom (aThat->mNetworkAdapters [slot]);
7255}
7256
7257/////////////////////////////////////////////////////////////////////////////
7258// SessionMachine class
7259/////////////////////////////////////////////////////////////////////////////
7260
7261/** Task structure for asynchronous VM operations */
7262struct SessionMachine::Task
7263{
7264 Task (SessionMachine *m, Progress *p)
7265 : machine (m), progress (p)
7266 , state (m->data()->mMachineState) // save the current machine state
7267 , subTask (false), settingsChanged (false)
7268 {}
7269
7270 void modifyLastState (MachineState_T s)
7271 {
7272 *const_cast <MachineState_T *> (&state) = s;
7273 }
7274
7275 virtual void handler() = 0;
7276
7277 const ComObjPtr <SessionMachine> machine;
7278 const ComObjPtr <Progress> progress;
7279 const MachineState_T state;
7280
7281 bool subTask : 1;
7282 bool settingsChanged : 1;
7283};
7284
7285/** Take snapshot task */
7286struct SessionMachine::TakeSnapshotTask : public SessionMachine::Task
7287{
7288 TakeSnapshotTask (SessionMachine *m)
7289 : Task (m, NULL) {}
7290
7291 void handler() { machine->takeSnapshotHandler (*this); }
7292};
7293
7294/** Discard snapshot task */
7295struct SessionMachine::DiscardSnapshotTask : public SessionMachine::Task
7296{
7297 DiscardSnapshotTask (SessionMachine *m, Progress *p, Snapshot *s)
7298 : Task (m, p)
7299 , snapshot (s) {}
7300
7301 DiscardSnapshotTask (const Task &task, Snapshot *s)
7302 : Task (task)
7303 , snapshot (s) {}
7304
7305 void handler() { machine->discardSnapshotHandler (*this); }
7306
7307 const ComObjPtr <Snapshot> snapshot;
7308};
7309
7310/** Discard current state task */
7311struct SessionMachine::DiscardCurrentStateTask : public SessionMachine::Task
7312{
7313 DiscardCurrentStateTask (SessionMachine *m, Progress *p,
7314 bool discardCurSnapshot)
7315 : Task (m, p), discardCurrentSnapshot (discardCurSnapshot) {}
7316
7317 void handler() { machine->discardCurrentStateHandler (*this); }
7318
7319 const bool discardCurrentSnapshot;
7320};
7321
7322////////////////////////////////////////////////////////////////////////////////
7323
7324DEFINE_EMPTY_CTOR_DTOR (SessionMachine)
7325
7326HRESULT SessionMachine::FinalConstruct()
7327{
7328 LogFlowThisFunc (("\n"));
7329
7330 /* set the proper type to indicate we're the SessionMachine instance */
7331 unconst (mType) = IsSessionMachine;
7332
7333#if defined(__WIN__)
7334 mIPCSem = NULL;
7335#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
7336 mIPCSem = -1;
7337#endif
7338
7339 return S_OK;
7340}
7341
7342void SessionMachine::FinalRelease()
7343{
7344 LogFlowThisFunc (("\n"));
7345
7346 uninit (Uninit::Unexpected);
7347}
7348
7349/**
7350 * @note Must be called only by Machine::openSession() from its own write lock.
7351 */
7352HRESULT SessionMachine::init (Machine *aMachine)
7353{
7354 LogFlowThisFuncEnter();
7355 LogFlowThisFunc (("mName={%ls}\n", aMachine->mUserData->mName.raw()));
7356
7357 AssertReturn (aMachine, E_INVALIDARG);
7358
7359 AssertReturn (aMachine->lockHandle()->isLockedOnCurrentThread(), E_FAIL);
7360
7361 /* Enclose the state transition NotReady->InInit->Ready */
7362 AutoInitSpan autoInitSpan (this);
7363 AssertReturn (autoInitSpan.isOk(), E_UNEXPECTED);
7364
7365 /* create the interprocess semaphore */
7366#if defined(__WIN__)
7367 mIPCSemName = aMachine->mData->mConfigFileFull;
7368 for (size_t i = 0; i < mIPCSemName.length(); i++)
7369 if (mIPCSemName[i] == '\\')
7370 mIPCSemName[i] = '/';
7371 mIPCSem = ::CreateMutex (NULL, FALSE, mIPCSemName);
7372 ComAssertMsgRet (mIPCSem, ("Cannot create IPC mutex, err=0x%08X", ::GetLastError()),
7373 E_FAIL);
7374#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
7375 Utf8Str configFile = aMachine->mData->mConfigFileFull;
7376 char *pszConfigFile = NULL;
7377 RTStrUtf8ToCurrentCP (&pszConfigFile, configFile);
7378 key_t key = ::ftok (pszConfigFile, 0);
7379 RTStrFree (pszConfigFile);
7380 mIPCSem = ::semget (key, 1, S_IRWXU | S_IRWXG | S_IRWXO | IPC_CREAT);
7381 ComAssertMsgRet (mIPCSem >= 0, ("Cannot create IPC semaphore, errno=%d", errno),
7382 E_FAIL);
7383 /* set the initial value to 1 */
7384 int rv = ::semctl (mIPCSem, 0, SETVAL, 1);
7385 ComAssertMsgRet (rv == 0, ("Cannot init IPC semaphore, errno=%d", errno),
7386 E_FAIL);
7387#endif
7388
7389 /* memorize the peer Machine */
7390 unconst (mPeer) = aMachine;
7391 /* share the parent pointer */
7392 unconst (mParent) = aMachine->mParent;
7393
7394 /* take the pointers to data to share */
7395 mData.share (aMachine->mData);
7396 mSSData.share (aMachine->mSSData);
7397
7398 mUserData.share (aMachine->mUserData);
7399 mHWData.share (aMachine->mHWData);
7400 mHDData.share (aMachine->mHDData);
7401
7402 unconst (mBIOSSettings).createObject();
7403 mBIOSSettings->init (this, aMachine->mBIOSSettings);
7404#ifdef VBOX_VRDP
7405 /* create another VRDPServer object that will be mutable */
7406 unconst (mVRDPServer).createObject();
7407 mVRDPServer->init (this, aMachine->mVRDPServer);
7408#endif
7409 /* create another DVD drive object that will be mutable */
7410 unconst (mDVDDrive).createObject();
7411 mDVDDrive->init (this, aMachine->mDVDDrive);
7412 /* create another floppy drive object that will be mutable */
7413 unconst (mFloppyDrive).createObject();
7414 mFloppyDrive->init (this, aMachine->mFloppyDrive);
7415 /* create another audio adapter object that will be mutable */
7416 unconst (mAudioAdapter).createObject();
7417 mAudioAdapter->init (this, aMachine->mAudioAdapter);
7418 /* create another USB controller object that will be mutable */
7419 unconst (mUSBController).createObject();
7420 mUSBController->init (this, aMachine->mUSBController);
7421 /* create a list of network adapters that will be mutable */
7422 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
7423 {
7424 unconst (mNetworkAdapters [slot]).createObject();
7425 mNetworkAdapters [slot]->init (this, aMachine->mNetworkAdapters [slot]);
7426 }
7427
7428 /* Confirm a successful initialization when it's the case */
7429 autoInitSpan.setSucceeded();
7430
7431 LogFlowThisFuncLeave();
7432 return S_OK;
7433}
7434
7435/**
7436 * Uninitializes this session object. If the reason is other than
7437 * Uninit::Unexpected, then this method MUST be called from #checkForDeath().
7438 *
7439 * @param aReason uninitialization reason
7440 *
7441 * @note Locks mParent + this object for writing.
7442 */
7443void SessionMachine::uninit (Uninit::Reason aReason)
7444{
7445 LogFlowThisFuncEnter();
7446 LogFlowThisFunc (("reason=%d\n", aReason));
7447
7448 /*
7449 * Strongly reference ourselves to prevent this object deletion after
7450 * mData->mSession.mMachine.setNull() below (which can release the last
7451 * reference and call the destructor). Important: this must be done before
7452 * accessing any members (and before AutoUninitSpan that does it as well).
7453 * This self reference will be released as the very last step on return.
7454 */
7455 ComObjPtr <SessionMachine> selfRef = this;
7456
7457 /* Enclose the state transition Ready->InUninit->NotReady */
7458 AutoUninitSpan autoUninitSpan (this);
7459 if (autoUninitSpan.uninitDone())
7460 {
7461 LogFlowThisFunc (("Already uninitialized\n"));
7462 LogFlowThisFuncLeave();
7463 return;
7464 }
7465
7466 if (autoUninitSpan.initFailed())
7467 {
7468 /*
7469 * We've been called by init() because it's failed. It's not really
7470 * necessary (nor it's safe) to perform the regular uninit sequence
7471 * below, the following is enough.
7472 */
7473 LogFlowThisFunc (("Initialization failed.\n"));
7474#if defined(__WIN__)
7475 if (mIPCSem)
7476 ::CloseHandle (mIPCSem);
7477 mIPCSem = NULL;
7478#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
7479 if (mIPCSem >= 0)
7480 ::semctl (mIPCSem, 0, IPC_RMID);
7481 mIPCSem = -1;
7482#endif
7483 uninitDataAndChildObjects();
7484 unconst (mParent).setNull();
7485 unconst (mPeer).setNull();
7486 LogFlowThisFuncLeave();
7487 return;
7488 }
7489
7490 /*
7491 * We need to lock this object in uninit() because the lock is shared
7492 * with mPeer (as well as data we modify below).
7493 * mParent->addProcessToReap() and others need mParent lock.
7494 */
7495 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
7496
7497 MachineState_T lastState = mData->mMachineState;
7498
7499 if (aReason == Uninit::Abnormal)
7500 {
7501 LogWarningThisFunc (("ABNORMAL client termination! (wasRunning=%d)\n",
7502 lastState >= MachineState_Running));
7503
7504 /* reset the state to Aborted */
7505 if (mData->mMachineState != MachineState_Aborted)
7506 setMachineState (MachineState_Aborted);
7507 }
7508
7509 if (isModified())
7510 {
7511 LogWarningThisFunc (("Discarding unsaved settings changes!\n"));
7512 rollback (false /* aNotify */);
7513 }
7514
7515 Assert (!mSnapshotData.mStateFilePath || !mSnapshotData.mSnapshot);
7516 if (mSnapshotData.mStateFilePath)
7517 {
7518 LogWarningThisFunc (("canceling failed save state request!\n"));
7519 endSavingState (FALSE /* aSuccess */);
7520 }
7521 else if (!!mSnapshotData.mSnapshot)
7522 {
7523 LogWarningThisFunc (("canceling untaken snapshot!\n"));
7524 endTakingSnapshot (FALSE /* aSuccess */);
7525 }
7526
7527 /* release all captured USB devices */
7528 if (aReason == Uninit::Abnormal && lastState >= MachineState_Running)
7529 {
7530 /* Console::captureUSBDevices() is called in the VM process only after
7531 * setting the machine state to Starting or Restoring.
7532 * Console::releaseAllUSBDevices() will be called upon successful
7533 * termination. So, we need to release USB devices only if there was
7534 * an abnormal termination of a running VM. */
7535 ReleaseAllUSBDevices();
7536 }
7537
7538 if (!mData->mSession.mType.isNull())
7539 {
7540 /* mType is not null when this machine's process has been started by
7541 * VirtualBox::OpenRemoteSession(), therefore it is our child. We
7542 * need to queue the PID to reap the process (and avoid zombies on
7543 * Linux). */
7544 Assert (mData->mSession.mPid != NIL_RTPROCESS);
7545 mParent->addProcessToReap (mData->mSession.mPid);
7546 }
7547
7548 mData->mSession.mPid = NIL_RTPROCESS;
7549
7550 if (aReason == Uninit::Unexpected)
7551 {
7552 /* Uninitialization didn't come from #checkForDeath(), so tell the
7553 * client watcher thread to update the set of machines that have open
7554 * sessions. */
7555 mParent->updateClientWatcher();
7556 }
7557
7558 /* uninitialize all remote controls */
7559 if (mData->mSession.mRemoteControls.size())
7560 {
7561 LogFlowThisFunc (("Closing remote sessions (%d):\n",
7562 mData->mSession.mRemoteControls.size()));
7563
7564 Data::Session::RemoteControlList::iterator it =
7565 mData->mSession.mRemoteControls.begin();
7566 while (it != mData->mSession.mRemoteControls.end())
7567 {
7568 LogFlowThisFunc ((" Calling remoteControl->Uninitialize()...\n"));
7569 HRESULT rc = (*it)->Uninitialize();
7570 LogFlowThisFunc ((" remoteControl->Uninitialize() returned %08X\n", rc));
7571 if (FAILED (rc))
7572 LogWarningThisFunc (("Forgot to close the remote session?\n"));
7573 ++ it;
7574 }
7575 mData->mSession.mRemoteControls.clear();
7576 }
7577
7578 /*
7579 * An expected uninitialization can come only from #checkForDeath().
7580 * Otherwise it means that something's got really wrong (for examlple,
7581 * the Session implementation has released the VirtualBox reference
7582 * before it triggered #OnSessionEnd(), or before releasing IPC semaphore,
7583 * etc). However, it's also possible, that the client releases the IPC
7584 * semaphore correctly (i.e. before it releases the VirtualBox reference),
7585 * but but the VirtualBox release event comes first to the server process.
7586 * This case is practically possible, so we should not assert on an
7587 * unexpected uninit, just log a warning.
7588 */
7589
7590 if ((aReason == Uninit::Unexpected))
7591 LogWarningThisFunc (("Unexpected SessionMachine uninitialization!\n"));
7592
7593 if (aReason != Uninit::Normal)
7594 mData->mSession.mDirectControl.setNull();
7595 else
7596 {
7597 /* this must be null here (see #OnSessionEnd()) */
7598 Assert (mData->mSession.mDirectControl.isNull());
7599 Assert (mData->mSession.mState == SessionState_SessionClosing);
7600 Assert (!mData->mSession.mProgress.isNull());
7601
7602 mData->mSession.mProgress->notifyComplete (S_OK);
7603 mData->mSession.mProgress.setNull();
7604 }
7605
7606 /* remove the association between the peer machine and this session machine */
7607 Assert (mData->mSession.mMachine == this ||
7608 aReason == Uninit::Unexpected);
7609
7610 /* reset the rest of session data */
7611 mData->mSession.mMachine.setNull();
7612 mData->mSession.mState = SessionState_SessionClosed;
7613 mData->mSession.mType.setNull();
7614
7615 /* close the interprocess semaphore before leaving the shared lock */
7616#if defined(__WIN__)
7617 if (mIPCSem)
7618 ::CloseHandle (mIPCSem);
7619 mIPCSem = NULL;
7620#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
7621 if (mIPCSem >= 0)
7622 ::semctl (mIPCSem, 0, IPC_RMID);
7623 mIPCSem = -1;
7624#endif
7625
7626 /* fire an event */
7627 mParent->onSessionStateChange (mData->mUuid, SessionState_SessionClosed);
7628
7629 uninitDataAndChildObjects();
7630
7631 /* leave the shared lock before setting the above two to NULL */
7632 alock.leave();
7633
7634 unconst (mParent).setNull();
7635 unconst (mPeer).setNull();
7636
7637 LogFlowThisFuncLeave();
7638}
7639
7640// AutoLock::Lockable interface
7641////////////////////////////////////////////////////////////////////////////////
7642
7643/**
7644 * Overrides VirtualBoxBase::lockHandle() in order to share the lock handle
7645 * with the primary Machine instance (mPeer).
7646 */
7647AutoLock::Handle *SessionMachine::lockHandle() const
7648{
7649 AssertReturn (!mPeer.isNull(), NULL);
7650 return mPeer->lockHandle();
7651}
7652
7653// IInternalMachineControl methods
7654////////////////////////////////////////////////////////////////////////////////
7655
7656/**
7657 * @note Locks the same as #setMachineState() does.
7658 */
7659STDMETHODIMP SessionMachine::UpdateState (MachineState_T machineState)
7660{
7661 return setMachineState (machineState);
7662}
7663
7664/**
7665 * @note Locks this object for reading.
7666 */
7667STDMETHODIMP SessionMachine::GetIPCId (BSTR *id)
7668{
7669 AutoCaller autoCaller (this);
7670 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7671
7672 AutoReaderLock alock (this);
7673
7674#if defined(__WIN__)
7675 mIPCSemName.cloneTo (id);
7676 return S_OK;
7677#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
7678 mData->mConfigFileFull.cloneTo (id);
7679 return S_OK;
7680#else
7681 return S_FAIL;
7682#endif
7683}
7684
7685/**
7686 * Goes through the USB filters of the given machine to see if the given
7687 * device matches any filter or not.
7688 *
7689 * @note Locks the same as USBController::hasMatchingFilter() does.
7690 */
7691STDMETHODIMP SessionMachine::RunUSBDeviceFilters (IUSBDevice *aUSBDevice,
7692 BOOL *aMatched)
7693{
7694 LogFlowThisFunc (("\n"));
7695
7696 if (!aUSBDevice)
7697 return E_INVALIDARG;
7698 if (!aMatched)
7699 return E_POINTER;
7700
7701 AutoCaller autoCaller (this);
7702 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7703
7704 *aMatched = mUSBController->hasMatchingFilter (aUSBDevice);
7705
7706 return S_OK;
7707}
7708
7709/**
7710 * @note Locks the same as Host::captureUSBDevice() does.
7711 */
7712STDMETHODIMP SessionMachine::CaptureUSBDevice (INPTR GUIDPARAM aId)
7713{
7714 LogFlowThisFunc (("\n"));
7715
7716 AutoCaller autoCaller (this);
7717 AssertComRCReturnRC (autoCaller.rc());
7718
7719 /* if cautureUSBDevice() fails, it must have set extended error info */
7720 return mParent->host()->captureUSBDevice (this, aId);
7721}
7722
7723/**
7724 * @note Locks the same as Host::releaseUSBDevice() does.
7725 */
7726STDMETHODIMP SessionMachine::ReleaseUSBDevice (INPTR GUIDPARAM aId)
7727{
7728 LogFlowThisFunc (("\n"));
7729
7730 AutoCaller autoCaller (this);
7731 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7732
7733 return mParent->host()->releaseUSBDevice (this, aId);
7734}
7735
7736/**
7737 * Inserts all machine filters to the USB proxy service and then calls
7738 * Host::autoCaptureUSBDevices().
7739 *
7740 * Called by Console from the VM process upon VM startup.
7741 *
7742 * @note Locks what called methods lock.
7743 */
7744STDMETHODIMP SessionMachine::AutoCaptureUSBDevices()
7745{
7746 LogFlowThisFunc (("\n"));
7747
7748 AutoCaller autoCaller (this);
7749 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7750
7751 HRESULT rc = mUSBController->notifyProxy (true /* aInsertFilters */);
7752 AssertComRC (rc);
7753 NOREF (rc);
7754
7755 return mParent->host()->autoCaptureUSBDevices (this);
7756}
7757
7758/**
7759 * Removes all machine filters from the USB proxy service and then calls
7760 * Host::releaseAllUSBDevices().
7761 *
7762 * Called by Console from the VM process upon normal VM termination or by
7763 * SessionMachine::uninit() upon abnormal VM termination (from under the
7764 * Machine/SessionMachine lock).
7765 *
7766 * @note Locks what called methods lock.
7767 */
7768STDMETHODIMP SessionMachine::ReleaseAllUSBDevices()
7769{
7770 LogFlowThisFunc (("\n"));
7771
7772 AutoCaller autoCaller (this);
7773 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7774
7775 HRESULT rc = mUSBController->notifyProxy (false /* aInsertFilters */);
7776 AssertComRC (rc);
7777 NOREF (rc);
7778
7779 return mParent->host()->releaseAllUSBDevices (this);
7780}
7781
7782/**
7783 * @note Locks mParent + this object for writing.
7784 */
7785STDMETHODIMP SessionMachine::OnSessionEnd (ISession *aSession,
7786 IProgress **aProgress)
7787{
7788 LogFlowThisFuncEnter();
7789
7790 AssertReturn (aSession, E_INVALIDARG);
7791 AssertReturn (aProgress, E_INVALIDARG);
7792
7793 AutoCaller autoCaller (this);
7794
7795 LogFlowThisFunc (("state=%d\n", autoCaller.state()));
7796 /*
7797 * We don't assert below because it might happen that a non-direct session
7798 * informs us it is closed right after we've been uninitialized -- it's ok.
7799 */
7800 CheckComRCReturnRC (autoCaller.rc());
7801
7802 /* get IInternalSessionControl interface */
7803 ComPtr <IInternalSessionControl> control (aSession);
7804
7805 ComAssertRet (!control.isNull(), E_INVALIDARG);
7806
7807 /* Progress::init() needs mParent lock */
7808 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
7809
7810 if (control.equalsTo (mData->mSession.mDirectControl))
7811 {
7812 ComAssertRet (aProgress, E_POINTER);
7813
7814 /* The direct session is being normally closed by the client process
7815 * ----------------------------------------------------------------- */
7816
7817 /* go to the closing state (essential for all open*Session() calls and
7818 * for #checkForDeath()) */
7819 Assert (mData->mSession.mState == SessionState_SessionOpen);
7820 mData->mSession.mState = SessionState_SessionClosing;
7821
7822 /* set direct control to NULL to release the remote instance */
7823 mData->mSession.mDirectControl.setNull();
7824 LogFlowThisFunc (("Direct control is set to NULL\n"));
7825
7826 /*
7827 * Create the progress object the client will use to wait until
7828 * #checkForDeath() is called to uninitialize this session object
7829 * after it releases the IPC semaphore.
7830 */
7831 ComObjPtr <Progress> progress;
7832 progress.createObject();
7833 progress->init (mParent, (IMachine *) mPeer, Bstr (tr ("Closing session")),
7834 FALSE /* aCancelable */);
7835 progress.queryInterfaceTo (aProgress);
7836 mData->mSession.mProgress = progress;
7837 }
7838 else
7839 {
7840 /* the remote session is being normally closed */
7841 Data::Session::RemoteControlList::iterator it =
7842 mData->mSession.mRemoteControls.begin();
7843 while (it != mData->mSession.mRemoteControls.end())
7844 {
7845 if (control.equalsTo (*it))
7846 break;
7847 ++it;
7848 }
7849 BOOL found = it != mData->mSession.mRemoteControls.end();
7850 ComAssertMsgRet (found, ("The session is not found in the session list!"),
7851 E_INVALIDARG);
7852 mData->mSession.mRemoteControls.remove (*it);
7853 }
7854
7855 LogFlowThisFuncLeave();
7856 return S_OK;
7857}
7858
7859/**
7860 * @note Locks mParent + this object for writing.
7861 */
7862STDMETHODIMP SessionMachine::BeginSavingState (IProgress *aProgress, BSTR *aStateFilePath)
7863{
7864 LogFlowThisFuncEnter();
7865
7866 AssertReturn (aProgress, E_INVALIDARG);
7867 AssertReturn (aStateFilePath, E_POINTER);
7868
7869 AutoCaller autoCaller (this);
7870 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7871
7872 /* mParent->addProgress() needs mParent lock */
7873 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
7874
7875 AssertReturn (mData->mMachineState == MachineState_Paused &&
7876 mSnapshotData.mLastState == MachineState_InvalidMachineState &&
7877 mSnapshotData.mProgressId.isEmpty() &&
7878 mSnapshotData.mStateFilePath.isNull(),
7879 E_FAIL);
7880
7881 /* memorize the progress ID and add it to the global collection */
7882 Guid progressId;
7883 HRESULT rc = aProgress->COMGETTER(Id) (progressId.asOutParam());
7884 AssertComRCReturn (rc, rc);
7885 rc = mParent->addProgress (aProgress);
7886 AssertComRCReturn (rc, rc);
7887
7888 Bstr stateFilePath;
7889 /* stateFilePath is null when the machine is not running */
7890 if (mData->mMachineState == MachineState_Paused)
7891 {
7892 stateFilePath = Utf8StrFmt ("%ls%c{%Vuuid}.sav",
7893 mUserData->mSnapshotFolderFull.raw(),
7894 RTPATH_DELIMITER, mData->mUuid.raw());
7895 }
7896
7897 /* fill in the snapshot data */
7898 mSnapshotData.mLastState = mData->mMachineState;
7899 mSnapshotData.mProgressId = progressId;
7900 mSnapshotData.mStateFilePath = stateFilePath;
7901
7902 /* set the state to Saving (this is expected by Console::SaveState()) */
7903 setMachineState (MachineState_Saving);
7904
7905 stateFilePath.cloneTo (aStateFilePath);
7906
7907 return S_OK;
7908}
7909
7910/**
7911 * @note Locks mParent + this objects for writing.
7912 */
7913STDMETHODIMP SessionMachine::EndSavingState (BOOL aSuccess)
7914{
7915 LogFlowThisFunc (("\n"));
7916
7917 AutoCaller autoCaller (this);
7918 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7919
7920 /* endSavingState() need mParent lock */
7921 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
7922
7923 AssertReturn (mData->mMachineState == MachineState_Saving &&
7924 mSnapshotData.mLastState != MachineState_InvalidMachineState &&
7925 !mSnapshotData.mProgressId.isEmpty() &&
7926 !mSnapshotData.mStateFilePath.isNull(),
7927 E_FAIL);
7928
7929 /*
7930 * on success, set the state to Saved;
7931 * on failure, set the state to the state we had when BeginSavingState() was
7932 * called (this is expected by Console::SaveState() and
7933 * Console::saveStateThread())
7934 */
7935 if (aSuccess)
7936 setMachineState (MachineState_Saved);
7937 else
7938 setMachineState (mSnapshotData.mLastState);
7939
7940 return endSavingState (aSuccess);
7941}
7942
7943/**
7944 * @note Locks mParent + this objects for writing.
7945 */
7946STDMETHODIMP SessionMachine::BeginTakingSnapshot (
7947 IConsole *aInitiator, INPTR BSTR aName, INPTR BSTR aDescription,
7948 IProgress *aProgress, BSTR *aStateFilePath,
7949 IProgress **aServerProgress)
7950{
7951 LogFlowThisFuncEnter();
7952
7953 AssertReturn (aInitiator && aName, E_INVALIDARG);
7954 AssertReturn (aStateFilePath && aServerProgress, E_POINTER);
7955
7956 LogFlowThisFunc (("aName='%ls'\n", aName));
7957
7958 AutoCaller autoCaller (this);
7959 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7960
7961 /* Progress::init() needs mParent lock */
7962 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
7963
7964 AssertReturn ((mData->mMachineState < MachineState_Running ||
7965 mData->mMachineState == MachineState_Paused) &&
7966 mSnapshotData.mLastState == MachineState_InvalidMachineState &&
7967 mSnapshotData.mSnapshot.isNull() &&
7968 mSnapshotData.mServerProgress.isNull() &&
7969 mSnapshotData.mCombinedProgress.isNull(),
7970 E_FAIL);
7971
7972 bool takingSnapshotOnline = mData->mMachineState == MachineState_Paused;
7973
7974 if (!takingSnapshotOnline && mData->mMachineState != MachineState_Saved)
7975 {
7976 /*
7977 * save all current settings to ensure current changes are committed
7978 * and hard disks are fixed up
7979 */
7980 HRESULT rc = saveSettings();
7981 CheckComRCReturnRC (rc);
7982 }
7983
7984 /* check that there are no Writethrough hard disks attached */
7985 for (HDData::HDAttachmentList::const_iterator
7986 it = mHDData->mHDAttachments.begin();
7987 it != mHDData->mHDAttachments.end();
7988 ++ it)
7989 {
7990 ComObjPtr <HardDisk> hd = (*it)->hardDisk();
7991 AutoLock hdLock (hd);
7992 if (hd->type() == HardDiskType_WritethroughHardDisk)
7993 return setError (E_FAIL,
7994 tr ("Cannot take a snapshot when there is a Writethrough hard "
7995 " disk attached ('%ls')"), hd->toString().raw());
7996 }
7997
7998 AssertReturn (aProgress || !takingSnapshotOnline, E_FAIL);
7999
8000 /* create an ID for the snapshot */
8001 Guid snapshotId;
8002 snapshotId.create();
8003
8004 Bstr stateFilePath;
8005 /* stateFilePath is null when the machine is not online nor saved */
8006 if (takingSnapshotOnline || mData->mMachineState == MachineState_Saved)
8007 stateFilePath = Utf8StrFmt ("%ls%c{%Vuuid}.sav",
8008 mUserData->mSnapshotFolderFull.raw(),
8009 RTPATH_DELIMITER,
8010 snapshotId.ptr());
8011
8012 /* ensure the directory for the saved state file exists */
8013 if (stateFilePath)
8014 {
8015 Utf8Str dir = stateFilePath;
8016 RTPathStripFilename (dir.mutableRaw());
8017 if (!RTDirExists (dir))
8018 {
8019 int vrc = RTDirCreateFullPath (dir, 0777);
8020 if (VBOX_FAILURE (vrc))
8021 return setError (E_FAIL,
8022 tr ("Could not create a directory '%s' to save the "
8023 "VM state to (%Vrc)"),
8024 dir.raw(), vrc);
8025 }
8026 }
8027
8028 /* create a snapshot machine object */
8029 ComObjPtr <SnapshotMachine> snapshotMachine;
8030 snapshotMachine.createObject();
8031 HRESULT rc = snapshotMachine->init (this, snapshotId, stateFilePath);
8032 AssertComRCReturn (rc, rc);
8033
8034 Bstr progressDesc = Bstr (tr ("Taking snapshot of virtual machine"));
8035 Bstr firstOpDesc = Bstr (tr ("Preparing to take snapshot"));
8036
8037 /*
8038 * create a server-side progress object (it will be descriptionless
8039 * when we need to combine it with the VM-side progress, i.e. when we're
8040 * taking a snapshot online). The number of operations is:
8041 * 1 (preparing) + # of VDIs + 1 (if the state is saved so we need to copy it)
8042 */
8043 ComObjPtr <Progress> serverProgress;
8044 {
8045 ULONG opCount = 1 + mHDData->mHDAttachments.size();
8046 if (mData->mMachineState == MachineState_Saved)
8047 opCount ++;
8048 serverProgress.createObject();
8049 if (takingSnapshotOnline)
8050 rc = serverProgress->init (FALSE, opCount, firstOpDesc);
8051 else
8052 rc = serverProgress->init (mParent, aInitiator, progressDesc, FALSE,
8053 opCount, firstOpDesc);
8054 AssertComRCReturn (rc, rc);
8055 }
8056
8057 /* create a combined server-side progress object when necessary */
8058 ComObjPtr <CombinedProgress> combinedProgress;
8059 if (takingSnapshotOnline)
8060 {
8061 combinedProgress.createObject();
8062 rc = combinedProgress->init (mParent, aInitiator, progressDesc,
8063 serverProgress, aProgress);
8064 AssertComRCReturn (rc, rc);
8065 }
8066
8067 /* create a snapshot object */
8068 RTTIMESPEC time;
8069 ComObjPtr <Snapshot> snapshot;
8070 snapshot.createObject();
8071 rc = snapshot->init (snapshotId, aName, aDescription,
8072 RTTimeSpecGetMilli (RTTimeNow (&time)),
8073 snapshotMachine, mData->mCurrentSnapshot);
8074 AssertComRCReturn (rc, rc);
8075
8076 /*
8077 * create and start the task on a separate thread
8078 * (note that it will not start working until we release alock)
8079 */
8080 TakeSnapshotTask *task = new TakeSnapshotTask (this);
8081 int vrc = RTThreadCreate (NULL, taskHandler,
8082 (void *) task,
8083 0, RTTHREADTYPE_MAIN_WORKER, 0, "TakeSnapshot");
8084 if (VBOX_FAILURE (vrc))
8085 {
8086 snapshot->uninit();
8087 delete task;
8088 ComAssertFailedRet (E_FAIL);
8089 }
8090
8091 /* fill in the snapshot data */
8092 mSnapshotData.mLastState = mData->mMachineState;
8093 mSnapshotData.mSnapshot = snapshot;
8094 mSnapshotData.mServerProgress = serverProgress;
8095 mSnapshotData.mCombinedProgress = combinedProgress;
8096
8097 /* set the state to Saving (this is expected by Console::TakeSnapshot()) */
8098 setMachineState (MachineState_Saving);
8099
8100 if (takingSnapshotOnline)
8101 stateFilePath.cloneTo (aStateFilePath);
8102 else
8103 *aStateFilePath = NULL;
8104
8105 serverProgress.queryInterfaceTo (aServerProgress);
8106
8107 LogFlowThisFuncLeave();
8108 return S_OK;
8109}
8110
8111/**
8112 * @note Locks mParent + this objects for writing.
8113 */
8114STDMETHODIMP SessionMachine::EndTakingSnapshot (BOOL aSuccess)
8115{
8116 LogFlowThisFunc (("\n"));
8117
8118 AutoCaller autoCaller (this);
8119 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8120
8121 /* Lock mParent because of endTakingSnapshot() */
8122 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
8123
8124 AssertReturn (!aSuccess ||
8125 (mData->mMachineState == MachineState_Saving &&
8126 mSnapshotData.mLastState != MachineState_InvalidMachineState &&
8127 !mSnapshotData.mSnapshot.isNull() &&
8128 !mSnapshotData.mServerProgress.isNull() &&
8129 !mSnapshotData.mCombinedProgress.isNull()),
8130 E_FAIL);
8131
8132 /*
8133 * set the state to the state we had when BeginTakingSnapshot() was called
8134 * (this is expected by Console::TakeSnapshot() and
8135 * Console::saveStateThread())
8136 */
8137 setMachineState (mSnapshotData.mLastState);
8138
8139 return endTakingSnapshot (aSuccess);
8140}
8141
8142/**
8143 * @note Locks mParent + this + children objects for writing!
8144 */
8145STDMETHODIMP SessionMachine::DiscardSnapshot (
8146 IConsole *aInitiator, INPTR GUIDPARAM aId,
8147 MachineState_T *aMachineState, IProgress **aProgress)
8148{
8149 LogFlowThisFunc (("\n"));
8150
8151 Guid id = aId;
8152 AssertReturn (aInitiator && !id.isEmpty(), E_INVALIDARG);
8153 AssertReturn (aMachineState && aProgress, E_POINTER);
8154
8155 AutoCaller autoCaller (this);
8156 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8157
8158 /* Progress::init() needs mParent lock */
8159 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
8160
8161 ComAssertRet (mData->mMachineState < MachineState_Running, E_FAIL);
8162
8163 ComObjPtr <Snapshot> snapshot;
8164 HRESULT rc = findSnapshot (id, snapshot, true /* aSetError */);
8165 CheckComRCReturnRC (rc);
8166
8167 AutoLock snapshotLock (snapshot);
8168 if (snapshot == mData->mFirstSnapshot)
8169 {
8170 AutoLock chLock (mData->mFirstSnapshot->childrenLock());
8171 size_t childrenCount = mData->mFirstSnapshot->children().size();
8172 if (childrenCount > 1)
8173 return setError (E_FAIL,
8174 tr ("Cannot discard the snapshot '%ls' because it is the first "
8175 "snapshot of the machine '%ls' and it has more than one "
8176 "child snapshot (%d)"),
8177 snapshot->data().mName.raw(), mUserData->mName.raw(),
8178 childrenCount);
8179 }
8180
8181 /*
8182 * If the snapshot being discarded is the current one, ensure current
8183 * settings are committed and saved.
8184 */
8185 if (snapshot == mData->mCurrentSnapshot)
8186 {
8187 if (isModified())
8188 {
8189 rc = saveSettings();
8190 CheckComRCReturnRC (rc);
8191 }
8192 }
8193
8194 /*
8195 * create a progress object. The number of operations is:
8196 * 1 (preparing) + # of VDIs
8197 */
8198 ComObjPtr <Progress> progress;
8199 progress.createObject();
8200 rc = progress->init (mParent, aInitiator,
8201 Bstr (Utf8StrFmt (tr ("Discarding snapshot '%ls'"),
8202 snapshot->data().mName.raw())),
8203 FALSE /* aCancelable */,
8204 1 + snapshot->data().mMachine->mHDData->mHDAttachments.size(),
8205 Bstr (tr ("Preparing to discard snapshot")));
8206 AssertComRCReturn (rc, rc);
8207
8208 /* create and start the task on a separate thread */
8209 DiscardSnapshotTask *task = new DiscardSnapshotTask (this, progress, snapshot);
8210 int vrc = RTThreadCreate (NULL, taskHandler,
8211 (void *) task,
8212 0, RTTHREADTYPE_MAIN_WORKER, 0, "DiscardSnapshot");
8213 if (VBOX_FAILURE (vrc))
8214 delete task;
8215 ComAssertRCRet (vrc, E_FAIL);
8216
8217 /* set the proper machine state (note: after creating a Task instance) */
8218 setMachineState (MachineState_Discarding);
8219
8220 /* return the progress to the caller */
8221 progress.queryInterfaceTo (aProgress);
8222
8223 /* return the new state to the caller */
8224 *aMachineState = mData->mMachineState;
8225
8226 return S_OK;
8227}
8228
8229/**
8230 * @note Locks mParent + this + children objects for writing!
8231 */
8232STDMETHODIMP SessionMachine::DiscardCurrentState (
8233 IConsole *aInitiator, MachineState_T *aMachineState, IProgress **aProgress)
8234{
8235 LogFlowThisFunc (("\n"));
8236
8237 AssertReturn (aInitiator, E_INVALIDARG);
8238 AssertReturn (aMachineState && aProgress, E_POINTER);
8239
8240 AutoCaller autoCaller (this);
8241 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8242
8243 /* Progress::init() needs mParent lock */
8244 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
8245
8246 ComAssertRet (mData->mMachineState < MachineState_Running, E_FAIL);
8247
8248 if (mData->mCurrentSnapshot.isNull())
8249 return setError (E_FAIL,
8250 tr ("Could not discard the current state of the machine '%ls' "
8251 "because it doesn't have any snapshots"),
8252 mUserData->mName.raw());
8253
8254 /*
8255 * create a progress object. The number of operations is:
8256 * 1 (preparing) + # of VDIs + 1 (if we need to copy the saved state file)
8257 */
8258 ComObjPtr <Progress> progress;
8259 progress.createObject();
8260 {
8261 ULONG opCount = 1 + mData->mCurrentSnapshot->data()
8262 .mMachine->mHDData->mHDAttachments.size();
8263 if (mData->mCurrentSnapshot->stateFilePath())
8264 ++ opCount;
8265 progress->init (mParent, aInitiator,
8266 Bstr (tr ("Discarding current machine state")),
8267 FALSE /* aCancelable */, opCount,
8268 Bstr (tr ("Preparing to discard current state")));
8269 }
8270
8271 /* create and start the task on a separate thread */
8272 DiscardCurrentStateTask *task =
8273 new DiscardCurrentStateTask (this, progress, false /* discardCurSnapshot */);
8274 int vrc = RTThreadCreate (NULL, taskHandler,
8275 (void *) task,
8276 0, RTTHREADTYPE_MAIN_WORKER, 0, "DiscardCurState");
8277 if (VBOX_FAILURE (vrc))
8278 delete task;
8279 ComAssertRCRet (vrc, E_FAIL);
8280
8281 /* set the proper machine state (note: after creating a Task instance) */
8282 setMachineState (MachineState_Discarding);
8283
8284 /* return the progress to the caller */
8285 progress.queryInterfaceTo (aProgress);
8286
8287 /* return the new state to the caller */
8288 *aMachineState = mData->mMachineState;
8289
8290 return S_OK;
8291}
8292
8293/**
8294 * @note Locks mParent + other objects for writing!
8295 */
8296STDMETHODIMP SessionMachine::DiscardCurrentSnapshotAndState (
8297 IConsole *aInitiator, MachineState_T *aMachineState, IProgress **aProgress)
8298{
8299 LogFlowThisFunc (("\n"));
8300
8301 AssertReturn (aInitiator, E_INVALIDARG);
8302 AssertReturn (aMachineState && aProgress, E_POINTER);
8303
8304 AutoCaller autoCaller (this);
8305 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8306
8307 /* Progress::init() needs mParent lock */
8308 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
8309
8310 ComAssertRet (mData->mMachineState < MachineState_Running, E_FAIL);
8311
8312 if (mData->mCurrentSnapshot.isNull())
8313 return setError (E_FAIL,
8314 tr ("Could not discard the current state of the machine '%ls' "
8315 "because it doesn't have any snapshots"),
8316 mUserData->mName.raw());
8317
8318 /*
8319 * create a progress object. The number of operations is:
8320 * 1 (preparing) + # of VDIs in the current snapshot +
8321 * # of VDIs in the previous snapshot +
8322 * 1 (if we need to copy the saved state file of the previous snapshot)
8323 * or (if there is no previous snapshot):
8324 * 1 (preparing) + # of VDIs in the current snapshot * 2 +
8325 * 1 (if we need to copy the saved state file of the current snapshot)
8326 */
8327 ComObjPtr <Progress> progress;
8328 progress.createObject();
8329 {
8330 ComObjPtr <Snapshot> curSnapshot = mData->mCurrentSnapshot;
8331 ComObjPtr <Snapshot> prevSnapshot = mData->mCurrentSnapshot->parent();
8332
8333 ULONG opCount = 1;
8334 if (prevSnapshot)
8335 {
8336 opCount += curSnapshot->data().mMachine->mHDData->mHDAttachments.size();
8337 opCount += prevSnapshot->data().mMachine->mHDData->mHDAttachments.size();
8338 if (prevSnapshot->stateFilePath())
8339 ++ opCount;
8340 }
8341 else
8342 {
8343 opCount += curSnapshot->data().mMachine->mHDData->mHDAttachments.size() * 2;
8344 if (curSnapshot->stateFilePath())
8345 ++ opCount;
8346 }
8347
8348 progress->init (mParent, aInitiator,
8349 Bstr (tr ("Discarding current machine snapshot and state")),
8350 FALSE /* aCancelable */, opCount,
8351 Bstr (tr ("Preparing to discard current snapshot and state")));
8352 }
8353
8354 /* create and start the task on a separate thread */
8355 DiscardCurrentStateTask *task =
8356 new DiscardCurrentStateTask (this, progress, true /* discardCurSnapshot */);
8357 int vrc = RTThreadCreate (NULL, taskHandler,
8358 (void *) task,
8359 0, RTTHREADTYPE_MAIN_WORKER, 0, "DiscardCurState");
8360 if (VBOX_FAILURE (vrc))
8361 delete task;
8362 ComAssertRCRet (vrc, E_FAIL);
8363
8364 /* set the proper machine state (note: after creating a Task instance) */
8365 setMachineState (MachineState_Discarding);
8366
8367 /* return the progress to the caller */
8368 progress.queryInterfaceTo (aProgress);
8369
8370 /* return the new state to the caller */
8371 *aMachineState = mData->mMachineState;
8372
8373 return S_OK;
8374}
8375
8376// public methods only for internal purposes
8377/////////////////////////////////////////////////////////////////////////////
8378
8379/**
8380 * Called from the client watcher thread to check for unexpected client
8381 * process death.
8382 *
8383 * @note On Win32, this method is called only when we've got the semaphore
8384 * (i.e. it has been signaled when we were waiting for it).
8385 *
8386 * On Win32, this method always returns true.
8387 *
8388 * On Linux, the method returns true if the client process has terminated
8389 * abnormally (and/or the session has been uninitialized) and false if it is
8390 * still alive.
8391 *
8392 * @note Locks this object for writing.
8393 */
8394bool SessionMachine::checkForDeath()
8395{
8396 Uninit::Reason reason;
8397 bool doUninit = false;
8398 bool rc = false;
8399
8400 /*
8401 * Enclose autoCaller with a block because calling uninit()
8402 * from under it will deadlock.
8403 */
8404 {
8405 AutoCaller autoCaller (this);
8406 if (!autoCaller.isOk())
8407 {
8408 /*
8409 * return true if not ready, to cause the client watcher to exclude
8410 * the corresponding session from watching
8411 */
8412 LogFlowThisFunc (("Already uninitialized!"));
8413 return true;
8414 }
8415
8416 AutoLock alock (this);
8417
8418 /*
8419 * Determine the reason of death: if the session state is Closing here,
8420 * everything is fine. Otherwise it means that the client did not call
8421 * OnSessionEnd() before it released the IPC semaphore.
8422 * This may happen either because the client process has abnormally
8423 * terminated, or because it simply forgot to call ISession::Close()
8424 * before exiting. We threat the latter also as an abnormal termination
8425 * (see Session::uninit() for details).
8426 */
8427 reason = mData->mSession.mState == SessionState_SessionClosing ?
8428 Uninit::Normal :
8429 Uninit::Abnormal;
8430
8431#if defined(__WIN__)
8432
8433 AssertMsg (mIPCSem, ("semaphore must be created"));
8434
8435 /* release the IPC mutex */
8436 ::ReleaseMutex (mIPCSem);
8437
8438 doUninit = true;
8439
8440 rc = true;
8441
8442#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
8443
8444 AssertMsg (mIPCSem >= 0, ("semaphore must be created"));
8445
8446 int val = ::semctl (mIPCSem, 0, GETVAL);
8447 if (val > 0)
8448 {
8449 /* the semaphore is signaled, meaning the session is terminated */
8450 doUninit = true;
8451 }
8452
8453 rc = val > 0;
8454
8455#endif
8456
8457 } /* AutoCaller block */
8458
8459 if (doUninit)
8460 uninit (reason);
8461
8462 return rc;
8463}
8464
8465/**
8466 * @note Locks this object for reading.
8467 */
8468HRESULT SessionMachine::onDVDDriveChange()
8469{
8470 LogFlowThisFunc (("\n"));
8471
8472 AutoCaller autoCaller (this);
8473 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8474
8475 ComPtr <IInternalSessionControl> directControl;
8476 {
8477 AutoReaderLock alock (this);
8478 directControl = mData->mSession.mDirectControl;
8479 }
8480
8481 /* ignore notifications sent after #OnSessionEnd() is called */
8482 if (!directControl)
8483 return S_OK;
8484
8485 return directControl->OnDVDDriveChange();
8486}
8487
8488/**
8489 * @note Locks this object for reading.
8490 */
8491HRESULT SessionMachine::onFloppyDriveChange()
8492{
8493 LogFlowThisFunc (("\n"));
8494
8495 AutoCaller autoCaller (this);
8496 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8497
8498 ComPtr <IInternalSessionControl> directControl;
8499 {
8500 AutoReaderLock alock (this);
8501 directControl = mData->mSession.mDirectControl;
8502 }
8503
8504 /* ignore notifications sent after #OnSessionEnd() is called */
8505 if (!directControl)
8506 return S_OK;
8507
8508 return directControl->OnFloppyDriveChange();
8509}
8510
8511/**
8512 * @note Locks this object for reading.
8513 */
8514HRESULT SessionMachine::onNetworkAdapterChange(INetworkAdapter *networkAdapter)
8515{
8516 LogFlowThisFunc (("\n"));
8517
8518 AutoCaller autoCaller (this);
8519 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8520
8521 ComPtr <IInternalSessionControl> directControl;
8522 {
8523 AutoReaderLock alock (this);
8524 directControl = mData->mSession.mDirectControl;
8525 }
8526
8527 /* ignore notifications sent after #OnSessionEnd() is called */
8528 if (!directControl)
8529 return S_OK;
8530
8531 return directControl->OnNetworkAdapterChange(networkAdapter);
8532}
8533
8534/**
8535 * @note Locks this object for reading.
8536 */
8537HRESULT SessionMachine::onVRDPServerChange()
8538{
8539 LogFlowThisFunc (("\n"));
8540
8541 AutoCaller autoCaller (this);
8542 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8543
8544 ComPtr <IInternalSessionControl> directControl;
8545 {
8546 AutoReaderLock alock (this);
8547 directControl = mData->mSession.mDirectControl;
8548 }
8549
8550 /* ignore notifications sent after #OnSessionEnd() is called */
8551 if (!directControl)
8552 return S_OK;
8553
8554 return directControl->OnVRDPServerChange();
8555}
8556
8557/**
8558 * @note Locks this object for reading.
8559 */
8560HRESULT SessionMachine::onUSBControllerChange()
8561{
8562 LogFlowThisFunc (("\n"));
8563
8564 AutoCaller autoCaller (this);
8565 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8566
8567 ComPtr <IInternalSessionControl> directControl;
8568 {
8569 AutoReaderLock alock (this);
8570 directControl = mData->mSession.mDirectControl;
8571 }
8572
8573 /* ignore notifications sent after #OnSessionEnd() is called */
8574 if (!directControl)
8575 return S_OK;
8576
8577 return directControl->OnUSBControllerChange();
8578}
8579
8580/**
8581 * @note Locks this object for reading.
8582 */
8583HRESULT SessionMachine::onUSBDeviceAttach (IUSBDevice *aDevice,
8584 IVirtualBoxErrorInfo *aError)
8585{
8586 LogFlowThisFunc (("\n"));
8587
8588 AutoCaller autoCaller (this);
8589
8590 /* This notification may happen after the machine object has been
8591 * uninitialized (the session was closed), so don't assert. */
8592 CheckComRCReturnRC (autoCaller.rc());
8593
8594 ComPtr <IInternalSessionControl> directControl;
8595 {
8596 AutoReaderLock alock (this);
8597 directControl = mData->mSession.mDirectControl;
8598 }
8599
8600 /* fail on notifications sent after #OnSessionEnd() is called, it is
8601 * expected by the caller */
8602 if (!directControl)
8603 return E_FAIL;
8604
8605 return directControl->OnUSBDeviceAttach (aDevice, aError);
8606}
8607
8608/**
8609 * @note Locks this object for reading.
8610 */
8611HRESULT SessionMachine::onUSBDeviceDetach (INPTR GUIDPARAM aId,
8612 IVirtualBoxErrorInfo *aError)
8613{
8614 LogFlowThisFunc (("\n"));
8615
8616 AutoCaller autoCaller (this);
8617
8618 /* This notification may happen after the machine object has been
8619 * uninitialized (the session was closed), so don't assert. */
8620 CheckComRCReturnRC (autoCaller.rc());
8621
8622 ComPtr <IInternalSessionControl> directControl;
8623 {
8624 AutoReaderLock alock (this);
8625 directControl = mData->mSession.mDirectControl;
8626 }
8627
8628 /* fail on notifications sent after #OnSessionEnd() is called, it is
8629 * expected by the caller */
8630 if (!directControl)
8631 return E_FAIL;
8632
8633 return directControl->OnUSBDeviceDetach (aId, aError);
8634}
8635
8636// protected methods
8637/////////////////////////////////////////////////////////////////////////////
8638
8639/**
8640 * Helper method to finalize saving the state.
8641 *
8642 * @note Must be called from under this object's lock.
8643 *
8644 * @param aSuccess TRUE if the snapshot has been taken successfully
8645 *
8646 * @note Locks mParent + this objects for writing.
8647 */
8648HRESULT SessionMachine::endSavingState (BOOL aSuccess)
8649{
8650 LogFlowThisFuncEnter();
8651
8652 AutoCaller autoCaller (this);
8653 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8654
8655 /* mParent->removeProgress() needs mParent lock */
8656 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
8657
8658 HRESULT rc = S_OK;
8659
8660 if (aSuccess)
8661 {
8662 mSSData->mStateFilePath = mSnapshotData.mStateFilePath;
8663
8664 /* save all VM settings */
8665 rc = saveSettings();
8666 }
8667 else
8668 {
8669 /* delete the saved state file (it might have been already created) */
8670 RTFileDelete (Utf8Str (mSnapshotData.mStateFilePath));
8671 }
8672
8673 /* remove the completed progress object */
8674 mParent->removeProgress (mSnapshotData.mProgressId);
8675
8676 /* clear out the temporary saved state data */
8677 mSnapshotData.mLastState = MachineState_InvalidMachineState;
8678 mSnapshotData.mProgressId.clear();
8679 mSnapshotData.mStateFilePath.setNull();
8680
8681 LogFlowThisFuncLeave();
8682 return rc;
8683}
8684
8685/**
8686 * Helper method to finalize taking a snapshot.
8687 * Gets called only from #EndTakingSnapshot() that is expected to
8688 * be called by the VM process when it finishes *all* the tasks related to
8689 * taking a snapshot, either scucessfully or unsuccessfilly.
8690 *
8691 * @param aSuccess TRUE if the snapshot has been taken successfully
8692 *
8693 * @note Locks mParent + this objects for writing.
8694 */
8695HRESULT SessionMachine::endTakingSnapshot (BOOL aSuccess)
8696{
8697 LogFlowThisFuncEnter();
8698
8699 AutoCaller autoCaller (this);
8700 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8701
8702 /* Progress object uninitialization needs mParent lock */
8703 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
8704
8705 HRESULT rc = S_OK;
8706
8707 if (aSuccess)
8708 {
8709 /* the server progress must be completed on success */
8710 Assert (mSnapshotData.mServerProgress->completed());
8711
8712 mData->mCurrentSnapshot = mSnapshotData.mSnapshot;
8713 /* memorize the first snapshot if necessary */
8714 if (!mData->mFirstSnapshot)
8715 mData->mFirstSnapshot = mData->mCurrentSnapshot;
8716
8717 int opFlags = SaveSS_AddOp | SaveSS_UpdateCurrentId;
8718 if (mSnapshotData.mLastState != MachineState_Paused && !isModified())
8719 {
8720 /*
8721 * the machine was powered off or saved when taking a snapshot,
8722 * so reset the mCurrentStateModified flag
8723 */
8724 mData->mCurrentStateModified = FALSE;
8725 opFlags |= SaveSS_UpdateCurStateModified;
8726 }
8727
8728 rc = saveSnapshotSettings (mSnapshotData.mSnapshot, opFlags);
8729 }
8730
8731 if (!aSuccess || FAILED (rc))
8732 {
8733 if (mSnapshotData.mSnapshot)
8734 {
8735 /* wait for the completion of the server progress (diff VDI creation) */
8736 /// @todo (dmik) later, we will definitely want to cancel it instead
8737 // (when the cancel function is implemented)
8738 mSnapshotData.mServerProgress->WaitForCompletion (-1);
8739
8740 /*
8741 * delete all differencing VDIs created
8742 * (this will attach their parents back)
8743 */
8744 rc = deleteSnapshotDiffs (mSnapshotData.mSnapshot);
8745 /* continue cleanup on error */
8746
8747 /* delete the saved state file (it might have been already created) */
8748 if (mSnapshotData.mSnapshot->stateFilePath())
8749 RTFileDelete (Utf8Str (mSnapshotData.mSnapshot->stateFilePath()));
8750
8751 mSnapshotData.mSnapshot->uninit();
8752 }
8753 }
8754
8755 /* inform callbacks */
8756 if (aSuccess && SUCCEEDED (rc))
8757 mParent->onSnapshotTaken (mData->mUuid, mSnapshotData.mSnapshot->data().mId);
8758
8759 /* clear out the snapshot data */
8760 mSnapshotData.mLastState = MachineState_InvalidMachineState;
8761 mSnapshotData.mSnapshot.setNull();
8762 mSnapshotData.mServerProgress.setNull();
8763 /* uninitialize the combined progress (to remove it from the VBox collection) */
8764 if (!mSnapshotData.mCombinedProgress.isNull())
8765 {
8766 mSnapshotData.mCombinedProgress->uninit();
8767 mSnapshotData.mCombinedProgress.setNull();
8768 }
8769
8770 LogFlowThisFuncLeave();
8771 return rc;
8772}
8773
8774/**
8775 * Take snapshot task handler.
8776 * Must be called only by TakeSnapshotTask::handler()!
8777 *
8778 * The sole purpose of this task is to asynchronously create differencing VDIs
8779 * and copy the saved state file (when necessary). The VM process will wait
8780 * for this task to complete using the mSnapshotData.mServerProgress
8781 * returned to it.
8782 *
8783 * @note Locks mParent + this objects for writing.
8784 */
8785void SessionMachine::takeSnapshotHandler (TakeSnapshotTask &aTask)
8786{
8787 LogFlowThisFuncEnter();
8788
8789 AutoCaller autoCaller (this);
8790
8791 LogFlowThisFunc (("state=%d\n", autoCaller.state()));
8792 if (!autoCaller.isOk())
8793 {
8794 /*
8795 * we might have been uninitialized because the session was
8796 * accidentally closed by the client, so don't assert
8797 */
8798 LogFlowThisFuncLeave();
8799 return;
8800 }
8801
8802 /* endTakingSnapshot() needs mParent lock */
8803 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
8804
8805 HRESULT rc = S_OK;
8806
8807 LogFlowThisFunc (("Creating differencing VDIs...\n"));
8808
8809 /* create new differencing hard disks and attach them to this machine */
8810 rc = createSnapshotDiffs (&mSnapshotData.mSnapshot->data().mId,
8811 mUserData->mSnapshotFolderFull,
8812 mSnapshotData.mServerProgress,
8813 true /* aOnline */);
8814
8815 if (SUCCEEDED (rc) && mSnapshotData.mLastState == MachineState_Saved)
8816 {
8817 Utf8Str stateFrom = mSSData->mStateFilePath;
8818 Utf8Str stateTo = mSnapshotData.mSnapshot->stateFilePath();
8819
8820 LogFlowThisFunc (("Copying the execution state from '%s' to '%s'...\n",
8821 stateFrom.raw(), stateTo.raw()));
8822
8823 mSnapshotData.mServerProgress->advanceOperation (
8824 Bstr (tr ("Copying the execution state")));
8825
8826 /*
8827 * We can safely leave the lock here:
8828 * mMachineState is MachineState_Saving here
8829 */
8830 alock.leave();
8831
8832 /* copy the state file */
8833 int vrc = RTFileCopyEx (stateFrom, stateTo, progressCallback,
8834 static_cast <Progress *> (mSnapshotData.mServerProgress));
8835
8836 alock.enter();
8837
8838 if (VBOX_FAILURE (vrc))
8839 rc = setError (E_FAIL,
8840 tr ("Could not copy the state file '%ls' to '%ls' (%Vrc)"),
8841 stateFrom.raw(), stateTo.raw());
8842 }
8843
8844 /*
8845 * we have to call endTakingSnapshot() here if the snapshot was taken
8846 * offline, because the VM process will not do it in this case
8847 */
8848 if (mSnapshotData.mLastState != MachineState_Paused)
8849 {
8850 LogFlowThisFunc (("Finalizing the taken snapshot (rc=%08X)...\n", rc));
8851
8852 setMachineState (mSnapshotData.mLastState);
8853 updateMachineStateOnClient();
8854
8855 /* finalize the progress after setting the state, for consistency */
8856 mSnapshotData.mServerProgress->notifyComplete (rc);
8857
8858 endTakingSnapshot (SUCCEEDED (rc));
8859 }
8860 else
8861 {
8862 mSnapshotData.mServerProgress->notifyComplete (rc);
8863 }
8864
8865 LogFlowThisFuncLeave();
8866}
8867
8868/**
8869 * Discard snapshot task handler.
8870 * Must be called only by DiscardSnapshotTask::handler()!
8871 *
8872 * When aTask.subTask is true, the associated progress object is left
8873 * uncompleted on success. On failure, the progress is marked as completed
8874 * regardless of this parameter.
8875 *
8876 * @note Locks mParent + this + child objects for writing!
8877 */
8878void SessionMachine::discardSnapshotHandler (DiscardSnapshotTask &aTask)
8879{
8880 LogFlowThisFuncEnter();
8881
8882 AutoCaller autoCaller (this);
8883
8884 LogFlowThisFunc (("state=%d\n", autoCaller.state()));
8885 if (!autoCaller.isOk())
8886 {
8887 /*
8888 * we might have been uninitialized because the session was
8889 * accidentally closed by the client, so don't assert
8890 */
8891 aTask.progress->notifyComplete (
8892 E_FAIL, COM_IIDOF (IMachine), getComponentName(),
8893 tr ("The session has been accidentally closed"));
8894
8895 LogFlowThisFuncLeave();
8896 return;
8897 }
8898
8899 ComObjPtr <SnapshotMachine> sm = aTask.snapshot->data().mMachine;
8900
8901 /* mParent is locked because of Progress::notifyComplete(), etc. */
8902 AutoMultiLock <3> alock (mParent->wlock(), this->wlock(), sm->rlock());
8903
8904 /* Safe locking in the direction parent->child */
8905 AutoLock snapshotLock (aTask.snapshot);
8906 AutoLock snapshotChildrenLock (aTask.snapshot->childrenLock());
8907
8908 HRESULT rc = S_OK;
8909
8910 /* save the snapshot ID (for callbacks) */
8911 Guid snapshotId = aTask.snapshot->data().mId;
8912
8913 do
8914 {
8915 /* first pass: */
8916 LogFlowThisFunc (("Check hard disk accessibility and affected machines...\n"));
8917
8918 HDData::HDAttachmentList::const_iterator it;
8919 for (it = sm->mHDData->mHDAttachments.begin();
8920 it != sm->mHDData->mHDAttachments.end();
8921 ++ it)
8922 {
8923 ComObjPtr <HardDiskAttachment> hda = *it;
8924 ComObjPtr <HardDisk> hd = hda->hardDisk();
8925 ComObjPtr <HardDisk> parent = hd->parent();
8926
8927 AutoLock hdLock (hd);
8928
8929 if (hd->hasForeignChildren())
8930 {
8931 rc = setError (E_FAIL,
8932 tr ("One or more hard disks belonging to other machines are "
8933 "based on the hard disk '%ls' stored in the snapshot '%ls'"),
8934 hd->toString().raw(), aTask.snapshot->data().mName.raw());
8935 break;
8936 }
8937
8938 if (hd->type() == HardDiskType_NormalHardDisk)
8939 {
8940 AutoLock hdChildrenLock (hd->childrenLock());
8941 size_t childrenCount = hd->children().size();
8942 if (childrenCount > 1)
8943 {
8944 rc = setError (E_FAIL,
8945 tr ("Normal hard disk '%ls' stored in the snapshot '%ls' "
8946 "has more than one child hard disk (%d)"),
8947 hd->toString().raw(), aTask.snapshot->data().mName.raw(),
8948 childrenCount);
8949 break;
8950 }
8951 }
8952 else
8953 {
8954 ComAssertMsgFailedBreak (("Invalid hard disk type %d\n", hd->type()),
8955 rc = E_FAIL);
8956 }
8957
8958 Bstr accessError;
8959 rc = hd->getAccessibleWithChildren (accessError);
8960 CheckComRCBreakRC (rc);
8961
8962 if (!accessError.isNull())
8963 {
8964 rc = setError (E_FAIL,
8965 tr ("Hard disk '%ls' stored in the snapshot '%ls' is not "
8966 "accessible (%ls)"),
8967 hd->toString().raw(), aTask.snapshot->data().mName.raw(),
8968 accessError.raw());
8969 break;
8970 }
8971
8972 rc = hd->setBusyWithChildren();
8973 if (FAILED (rc))
8974 {
8975 /* reset the busy flag of all previous hard disks */
8976 while (it != sm->mHDData->mHDAttachments.begin())
8977 (*(-- it))->hardDisk()->clearBusyWithChildren();
8978 break;
8979 }
8980 }
8981
8982 CheckComRCBreakRC (rc);
8983
8984 /* second pass: */
8985 LogFlowThisFunc (("Performing actual vdi merging...\n"));
8986
8987 for (it = sm->mHDData->mHDAttachments.begin();
8988 it != sm->mHDData->mHDAttachments.end();
8989 ++ it)
8990 {
8991 ComObjPtr <HardDiskAttachment> hda = *it;
8992 ComObjPtr <HardDisk> hd = hda->hardDisk();
8993 ComObjPtr <HardDisk> parent = hd->parent();
8994
8995 AutoLock hdLock (hd);
8996
8997 Bstr hdRootString = hd->root()->toString (true /* aShort */);
8998
8999 if (parent)
9000 {
9001 if (hd->isParentImmutable())
9002 {
9003 aTask.progress->advanceOperation (Bstr (Utf8StrFmt (
9004 tr ("Discarding changes to immutable hard disk '%ls'"),
9005 hdRootString.raw())));
9006
9007 /* clear the busy flag before unregistering */
9008 hd->clearBusy();
9009
9010 /*
9011 * unregisterDiffHardDisk() is supposed to delete and uninit
9012 * the differencing hard disk
9013 */
9014 rc = mParent->unregisterDiffHardDisk (hd);
9015 CheckComRCBreakRC (rc);
9016 continue;
9017 }
9018 else
9019 {
9020 /*
9021 * differencing VDI:
9022 * merge this image to all its children
9023 */
9024
9025 aTask.progress->advanceOperation (Bstr (Utf8StrFmt (
9026 tr ("Merging changes to normal hard disk '%ls' to children"),
9027 hdRootString.raw())));
9028
9029 snapshotChildrenLock.unlock();
9030 snapshotLock.unlock();
9031 alock.leave();
9032
9033 rc = hd->asVDI()->mergeImageToChildren (aTask.progress);
9034
9035 alock.enter();
9036 snapshotLock.lock();
9037 snapshotChildrenLock.lock();
9038
9039 // debug code
9040 // if (it != sm->mHDData->mHDAttachments.begin())
9041 // {
9042 // rc = setError (E_FAIL, "Simulated failure");
9043 // break;
9044 //}
9045
9046 if (SUCCEEDED (rc))
9047 rc = mParent->unregisterDiffHardDisk (hd);
9048 else
9049 hd->clearBusyWithChildren();
9050
9051 CheckComRCBreakRC (rc);
9052 }
9053 }
9054 else if (hd->type() == HardDiskType_NormalHardDisk)
9055 {
9056 /*
9057 * normal vdi has the only child or none
9058 * (checked in the first pass)
9059 */
9060
9061 ComObjPtr <HardDisk> child;
9062 {
9063 AutoLock hdChildrenLock (hd->childrenLock());
9064 if (hd->children().size())
9065 child = hd->children().front();
9066 }
9067
9068 if (child.isNull())
9069 {
9070 aTask.progress->advanceOperation (Bstr (Utf8StrFmt (
9071 tr ("Detaching normal hard disk '%ls'"),
9072 hdRootString.raw())));
9073
9074 /* just deassociate the normal image from this machine */
9075 hd->setMachineId (Guid());
9076 hd->setSnapshotId (Guid());
9077
9078 /* clear the busy flag */
9079 hd->clearBusy();
9080 }
9081 else
9082 {
9083 AutoLock childLock (child);
9084
9085 aTask.progress->advanceOperation (Bstr (Utf8StrFmt (
9086 tr ("Preserving changes to normal hard disk '%ls'"),
9087 hdRootString.raw())));
9088
9089 ComObjPtr <Machine> cm;
9090 ComObjPtr <Snapshot> cs;
9091 ComObjPtr <HardDiskAttachment> childHda;
9092 rc = findHardDiskAttachment (child, &cm, &cs, &childHda);
9093 CheckComRCBreakRC (rc);
9094 /* must be the same machine (checked in the first pass) */
9095 ComAssertBreak (cm->mData->mUuid == mData->mUuid, rc = E_FAIL);
9096
9097 /* merge the child to this basic image */
9098
9099 snapshotChildrenLock.unlock();
9100 snapshotLock.unlock();
9101 alock.leave();
9102
9103 rc = child->asVDI()->mergeImageToParent (aTask.progress);
9104
9105 alock.enter();
9106 snapshotLock.lock();
9107 snapshotChildrenLock.lock();
9108
9109 if (SUCCEEDED (rc))
9110 rc = mParent->unregisterDiffHardDisk (child);
9111 else
9112 hd->clearBusyWithChildren();
9113
9114 CheckComRCBreakRC (rc);
9115
9116 /* reset the snapshot Id */
9117 hd->setSnapshotId (Guid());
9118
9119 /* replace the child image in the appropriate place */
9120 childHda->updateHardDisk (hd, FALSE /* aDirty */);
9121
9122 if (!cs)
9123 {
9124 aTask.settingsChanged = true;
9125 }
9126 else
9127 {
9128 rc = cm->saveSnapshotSettings (cs, SaveSS_UpdateAllOp);
9129 CheckComRCBreakRC (rc);
9130 }
9131 }
9132 }
9133 else
9134 {
9135 ComAssertMsgFailedBreak (("Invalid hard disk type %d\n", hd->type()),
9136 rc = E_FAIL);
9137 }
9138 }
9139
9140 /* preserve existing error info */
9141 ErrorInfoKeeper mergeEik;
9142 HRESULT mergeRc = rc;
9143
9144 if (FAILED (rc))
9145 {
9146 /* clear the busy flag on the rest of hard disks */
9147 for (++ it; it != sm->mHDData->mHDAttachments.end(); ++ it)
9148 (*it)->hardDisk()->clearBusyWithChildren();
9149 }
9150
9151 /*
9152 * we have to try to discard the snapshot even if merging failed
9153 * because some images might have been already merged (and deleted)
9154 */
9155
9156 do
9157 {
9158 LogFlowThisFunc (("Discarding the snapshot (reparenting children)...\n"));
9159
9160 ComObjPtr <Snapshot> parentSnapshot = aTask.snapshot->parent();
9161
9162 /// @todo (dmik):
9163 // when we introduce clones later, discarding the snapshot
9164 // will affect the current and first snapshots of clones, if they are
9165 // direct children of this snapshot. So we will need to lock machines
9166 // associated with child snapshots as well and update mCurrentSnapshot
9167 // and/or mFirstSnapshot fields.
9168
9169 if (aTask.snapshot == mData->mCurrentSnapshot)
9170 {
9171 /* currently, the parent snapshot must refer to the same machine */
9172 ComAssertBreak (
9173 !parentSnapshot ||
9174 parentSnapshot->data().mMachine->mData->mUuid == mData->mUuid,
9175 rc = E_FAIL);
9176 mData->mCurrentSnapshot = parentSnapshot;
9177 /* mark the current state as modified */
9178 mData->mCurrentStateModified = TRUE;
9179 }
9180
9181 if (aTask.snapshot == mData->mFirstSnapshot)
9182 {
9183 /*
9184 * the first snapshot must have only one child when discarded,
9185 * or no children at all
9186 */
9187 ComAssertBreak (aTask.snapshot->children().size() <= 1, rc = E_FAIL);
9188
9189 if (aTask.snapshot->children().size() == 1)
9190 {
9191 ComObjPtr <Snapshot> childSnapshot = aTask.snapshot->children().front();
9192 ComAssertBreak (
9193 childSnapshot->data().mMachine->mData->mUuid == mData->mUuid,
9194 rc = E_FAIL);
9195 mData->mFirstSnapshot = childSnapshot;
9196 }
9197 else
9198 mData->mFirstSnapshot.setNull();
9199 }
9200
9201 /// @todo (dmik)
9202 // if we implement some warning mechanism later, we'll have
9203 // to return a warning if the state file path cannot be deleted
9204 Bstr stateFilePath = aTask.snapshot->stateFilePath();
9205 if (stateFilePath)
9206 RTFileDelete (Utf8Str (stateFilePath));
9207
9208 aTask.snapshot->discard();
9209
9210 rc = saveSnapshotSettings (parentSnapshot,
9211 SaveSS_UpdateAllOp | SaveSS_UpdateCurrentId);
9212 }
9213 while (0);
9214
9215 /* restore the merge error if any (ErrorInfo will be restored
9216 * automatically) */
9217 if (FAILED (mergeRc))
9218 rc = mergeRc;
9219 }
9220 while (0);
9221
9222 if (!aTask.subTask || FAILED (rc))
9223 {
9224 if (!aTask.subTask)
9225 {
9226 /* preserve existing error info */
9227 ErrorInfoKeeper eik;
9228
9229 /* restore the machine state */
9230 setMachineState (aTask.state);
9231 updateMachineStateOnClient();
9232
9233 /*
9234 * save settings anyway, since we've already changed the current
9235 * machine configuration
9236 */
9237 if (aTask.settingsChanged)
9238 {
9239 saveSettings (true /* aMarkCurStateAsModified */,
9240 true /* aInformCallbacksAnyway */);
9241 }
9242 }
9243
9244 /* set the result (this will try to fetch current error info on failure) */
9245 aTask.progress->notifyComplete (rc);
9246 }
9247
9248 if (SUCCEEDED (rc))
9249 mParent->onSnapshotDiscarded (mData->mUuid, snapshotId);
9250
9251 LogFlowThisFunc (("Done discarding snapshot (rc=%08X)\n", rc));
9252 LogFlowThisFuncLeave();
9253}
9254
9255/**
9256 * Discard current state task handler.
9257 * Must be called only by DiscardCurrentStateTask::handler()!
9258 *
9259 * @note Locks mParent + this object for writing.
9260 */
9261void SessionMachine::discardCurrentStateHandler (DiscardCurrentStateTask &aTask)
9262{
9263 LogFlowThisFuncEnter();
9264
9265 AutoCaller autoCaller (this);
9266
9267 LogFlowThisFunc (("state=%d\n", autoCaller.state()));
9268 if (!autoCaller.isOk())
9269 {
9270 /*
9271 * we might have been uninitialized because the session was
9272 * accidentally closed by the client, so don't assert
9273 */
9274 aTask.progress->notifyComplete (
9275 E_FAIL, COM_IIDOF (IMachine), getComponentName(),
9276 tr ("The session has been accidentally closed"));
9277
9278 LogFlowThisFuncLeave();
9279 return;
9280 }
9281
9282 /* mParent is locked because of Progress::notifyComplete(), etc. */
9283 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
9284
9285 /*
9286 * discard all current changes to mUserData (name, OSType etc.)
9287 * (note that the machine is powered off, so there is no need
9288 * to inform the direct session)
9289 */
9290 if (isModified())
9291 rollback (false /* aNotify */);
9292
9293 HRESULT rc = S_OK;
9294
9295 bool errorInSubtask = false;
9296 bool stateRestored = false;
9297
9298 const bool isLastSnapshot = mData->mCurrentSnapshot->parent().isNull();
9299
9300 do
9301 {
9302 /*
9303 * discard the saved state file if the machine was Saved prior
9304 * to this operation
9305 */
9306 if (aTask.state == MachineState_Saved)
9307 {
9308 Assert (!mSSData->mStateFilePath.isEmpty());
9309 RTFileDelete (Utf8Str (mSSData->mStateFilePath));
9310 mSSData->mStateFilePath.setNull();
9311 aTask.modifyLastState (MachineState_PoweredOff);
9312 rc = saveStateSettings (SaveSTS_StateFilePath);
9313 CheckComRCBreakRC (rc);
9314 }
9315
9316 if (aTask.discardCurrentSnapshot && !isLastSnapshot)
9317 {
9318 /*
9319 * the "discard current snapshot and state" task is in action,
9320 * the current snapshot is not the last one.
9321 * Discard the current snapshot first.
9322 */
9323
9324 DiscardSnapshotTask subTask (aTask, mData->mCurrentSnapshot);
9325 subTask.subTask = true;
9326 discardSnapshotHandler (subTask);
9327 aTask.settingsChanged = subTask.settingsChanged;
9328 if (aTask.progress->completed())
9329 {
9330 /*
9331 * the progress can be completed by a subtask only if there was
9332 * a failure
9333 */
9334 Assert (FAILED (aTask.progress->resultCode()));
9335 errorInSubtask = true;
9336 rc = aTask.progress->resultCode();
9337 break;
9338 }
9339 }
9340
9341 LONG64 snapshotTimeStamp = 0;
9342
9343 {
9344 ComObjPtr <Snapshot> curSnapshot = mData->mCurrentSnapshot;
9345 AutoLock snapshotLock (curSnapshot);
9346
9347 /* remember the timestamp of the snapshot we're restoring from */
9348 snapshotTimeStamp = curSnapshot->data().mTimeStamp;
9349
9350 /* copy all hardware data from the current snapshot */
9351 copyFrom (curSnapshot->data().mMachine);
9352
9353 LogFlowThisFunc (("Restoring VDIs from the snapshot...\n"));
9354
9355 /* restore the attachmends from the snapshot */
9356 mHDData.backup();
9357 mHDData->mHDAttachments =
9358 curSnapshot->data().mMachine->mHDData->mHDAttachments;
9359
9360 snapshotLock.unlock();
9361 alock.leave();
9362 rc = createSnapshotDiffs (NULL, mUserData->mSnapshotFolderFull,
9363 aTask.progress,
9364 false /* aOnline */);
9365 alock.enter();
9366 snapshotLock.lock();
9367
9368 if (FAILED (rc))
9369 {
9370 /* here we can still safely rollback, so do it */
9371 /* preserve existing error info */
9372 ErrorInfoKeeper eik;
9373 /* undo all changes */
9374 rollback (false /* aNotify */);
9375 break;
9376 }
9377
9378 /*
9379 * note: old VDIs will be deassociated/deleted on #commit() called
9380 * either from #saveSettings() or directly at the end
9381 */
9382
9383 /* should not have a saved state file associated at this point */
9384 Assert (mSSData->mStateFilePath.isNull());
9385
9386 if (curSnapshot->stateFilePath())
9387 {
9388 Utf8Str snapStateFilePath = curSnapshot->stateFilePath();
9389
9390 Utf8Str stateFilePath = Utf8StrFmt ("%ls%c{%Vuuid}.sav",
9391 mUserData->mSnapshotFolderFull.raw(),
9392 RTPATH_DELIMITER, mData->mUuid.raw());
9393
9394 LogFlowThisFunc (("Copying saved state file from '%s' to '%s'...\n",
9395 snapStateFilePath.raw(), stateFilePath.raw()));
9396
9397 aTask.progress->advanceOperation (
9398 Bstr (tr ("Restoring the execution state")));
9399
9400 /* copy the state file */
9401 snapshotLock.unlock();
9402 alock.leave();
9403 int vrc = RTFileCopyEx (snapStateFilePath, stateFilePath,
9404 progressCallback, aTask.progress);
9405 alock.enter();
9406 snapshotLock.lock();
9407
9408 if (VBOX_SUCCESS (vrc))
9409 {
9410 mSSData->mStateFilePath = stateFilePath;
9411 }
9412 else
9413 {
9414 rc = setError (E_FAIL,
9415 tr ("Could not copy the state file '%s' to '%s' (%Vrc)"),
9416 snapStateFilePath.raw(), stateFilePath.raw(), vrc);
9417 break;
9418 }
9419 }
9420 }
9421
9422 bool informCallbacks = false;
9423
9424 if (aTask.discardCurrentSnapshot && isLastSnapshot)
9425 {
9426 /*
9427 * discard the current snapshot and state task is in action,
9428 * the current snapshot is the last one.
9429 * Discard the current snapshot after discarding the current state.
9430 */
9431
9432 /* commit changes to fixup hard disks before discarding */
9433 rc = commit();
9434 if (SUCCEEDED (rc))
9435 {
9436 DiscardSnapshotTask subTask (aTask, mData->mCurrentSnapshot);
9437 subTask.subTask = true;
9438 discardSnapshotHandler (subTask);
9439 aTask.settingsChanged = subTask.settingsChanged;
9440 if (aTask.progress->completed())
9441 {
9442 /*
9443 * the progress can be completed by a subtask only if there
9444 * was a failure
9445 */
9446 Assert (FAILED (aTask.progress->resultCode()));
9447 errorInSubtask = true;
9448 rc = aTask.progress->resultCode();
9449 }
9450 }
9451
9452 /*
9453 * we've committed already, so inform callbacks anyway to ensure
9454 * they don't miss some change
9455 */
9456 informCallbacks = true;
9457 }
9458
9459 /*
9460 * we have already discarded the current state, so set the
9461 * execution state accordingly no matter of the discard snapshot result
9462 */
9463 if (mSSData->mStateFilePath)
9464 setMachineState (MachineState_Saved);
9465 else
9466 setMachineState (MachineState_PoweredOff);
9467
9468 updateMachineStateOnClient();
9469 stateRestored = true;
9470
9471 if (errorInSubtask)
9472 break;
9473
9474 /* assign the timestamp from the snapshot */
9475 Assert (snapshotTimeStamp != 0);
9476 mData->mLastStateChange = snapshotTimeStamp;
9477
9478 /* mark the current state as not modified */
9479 mData->mCurrentStateModified = FALSE;
9480
9481 /* save all settings and commit */
9482 rc = saveSettings (false /* aMarkCurStateAsModified */,
9483 informCallbacks);
9484 aTask.settingsChanged = false;
9485 }
9486 while (0);
9487
9488 if (FAILED (rc))
9489 {
9490 /* preserve existing error info */
9491 ErrorInfoKeeper eik;
9492
9493 if (!stateRestored)
9494 {
9495 /* restore the machine state */
9496 setMachineState (aTask.state);
9497 updateMachineStateOnClient();
9498 }
9499
9500 /*
9501 * save all settings and commit if still modified (there is no way to
9502 * rollback properly). Note that isModified() will return true after
9503 * copyFrom(). Also save the settings if requested by the subtask.
9504 */
9505 if (isModified() || aTask.settingsChanged)
9506 {
9507 if (aTask.settingsChanged)
9508 saveSettings (true /* aMarkCurStateAsModified */,
9509 true /* aInformCallbacksAnyway */);
9510 else
9511 saveSettings();
9512 }
9513 }
9514
9515 if (!errorInSubtask)
9516 {
9517 /* set the result (this will try to fetch current error info on failure) */
9518 aTask.progress->notifyComplete (rc);
9519 }
9520
9521 if (SUCCEEDED (rc))
9522 mParent->onSnapshotDiscarded (mData->mUuid, Guid());
9523
9524 LogFlowThisFunc (("Done discarding current state (rc=%08X)\n", rc));
9525
9526 LogFlowThisFuncLeave();
9527}
9528
9529/**
9530 * Helper to change the machine state (reimplementation).
9531 *
9532 * @note Locks this object for writing.
9533 */
9534HRESULT SessionMachine::setMachineState (MachineState_T aMachineState)
9535{
9536 LogFlowThisFuncEnter();
9537 LogFlowThisFunc (("aMachineState=%d\n", aMachineState));
9538
9539 AutoCaller autoCaller (this);
9540 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
9541
9542 AutoLock alock (this);
9543
9544 MachineState_T oldMachineState = mData->mMachineState;
9545
9546 AssertMsgReturn (oldMachineState != aMachineState,
9547 ("oldMachineState=%d, aMachineState=%d\n",
9548 oldMachineState, aMachineState), E_FAIL);
9549
9550 HRESULT rc = S_OK;
9551
9552 int stsFlags = 0;
9553 bool deleteSavedState = false;
9554
9555 /* detect some state transitions */
9556
9557 if (oldMachineState < MachineState_Running &&
9558 aMachineState >= MachineState_Running &&
9559 aMachineState != MachineState_Discarding)
9560 {
9561 /*
9562 * the EMT thread is about to start, so mark attached HDDs as busy
9563 * and all its ancestors as being in use
9564 */
9565 for (HDData::HDAttachmentList::const_iterator it =
9566 mHDData->mHDAttachments.begin();
9567 it != mHDData->mHDAttachments.end();
9568 ++ it)
9569 {
9570 ComObjPtr <HardDisk> hd = (*it)->hardDisk();
9571 AutoLock hdLock (hd);
9572 hd->setBusy();
9573 hd->addReaderOnAncestors();
9574 }
9575 }
9576 else
9577 if (oldMachineState >= MachineState_Running &&
9578 oldMachineState != MachineState_Discarding &&
9579 aMachineState < MachineState_Running)
9580 {
9581 /*
9582 * the EMT thread stopped, so mark attached HDDs as no more busy
9583 * and remove the in-use flag from all its ancestors
9584 */
9585 for (HDData::HDAttachmentList::const_iterator it =
9586 mHDData->mHDAttachments.begin();
9587 it != mHDData->mHDAttachments.end();
9588 ++ it)
9589 {
9590 ComObjPtr <HardDisk> hd = (*it)->hardDisk();
9591 AutoLock hdLock (hd);
9592 hd->releaseReaderOnAncestors();
9593 hd->clearBusy();
9594 }
9595 }
9596
9597 if (oldMachineState == MachineState_Restoring)
9598 {
9599 if (aMachineState != MachineState_Saved)
9600 {
9601 /*
9602 * delete the saved state file once the machine has finished
9603 * restoring from it (note that Console sets the state from
9604 * Restoring to Saved if the VM couldn't restore successfully,
9605 * to give the user an ability to fix an error and retry --
9606 * we keep the saved state file in this case)
9607 */
9608 deleteSavedState = true;
9609 }
9610 }
9611 else
9612 if (oldMachineState == MachineState_Saved &&
9613 (aMachineState == MachineState_PoweredOff ||
9614 aMachineState == MachineState_Aborted))
9615 {
9616 /*
9617 * delete the saved state after Console::DiscardSavedState() is called
9618 * or if the VM process (owning a direct VM session) crashed while the
9619 * VM was Saved
9620 */
9621
9622 /// @todo (dmik)
9623 // Not sure that deleting the saved state file just because of the
9624 // client death before it attempted to restore the VM is a good
9625 // thing. But when it crashes we need to go to the Aborted state
9626 // which cannot have the saved state file associated... The only
9627 // way to fix this is to make the Aborted condition not a VM state
9628 // but a bool flag: i.e., when a crash occurs, set it to true and
9629 // change the state to PoweredOff or Saved depending on the
9630 // saved state presence.
9631
9632 deleteSavedState = true;
9633 mData->mCurrentStateModified = TRUE;
9634 stsFlags |= SaveSTS_CurStateModified;
9635 }
9636
9637 if (aMachineState == MachineState_Starting ||
9638 aMachineState == MachineState_Restoring)
9639 {
9640 /*
9641 * set the current state modified flag to indicate that the
9642 * current state is no more identical to the state in the
9643 * current snapshot
9644 */
9645 if (!mData->mCurrentSnapshot.isNull())
9646 {
9647 mData->mCurrentStateModified = TRUE;
9648 stsFlags |= SaveSTS_CurStateModified;
9649 }
9650 }
9651
9652 if (deleteSavedState == true)
9653 {
9654 Assert (!mSSData->mStateFilePath.isEmpty());
9655 RTFileDelete (Utf8Str (mSSData->mStateFilePath));
9656 mSSData->mStateFilePath.setNull();
9657 stsFlags |= SaveSTS_StateFilePath;
9658 }
9659
9660 /* redirect to the underlying peer machine */
9661 mPeer->setMachineState (aMachineState);
9662
9663 if (aMachineState == MachineState_PoweredOff ||
9664 aMachineState == MachineState_Aborted ||
9665 aMachineState == MachineState_Saved)
9666 {
9667 stsFlags |= SaveSTS_StateTimeStamp;
9668 }
9669
9670 rc = saveStateSettings (stsFlags);
9671
9672 if ((oldMachineState != MachineState_PoweredOff &&
9673 oldMachineState != MachineState_Aborted) &&
9674 (aMachineState == MachineState_PoweredOff ||
9675 aMachineState == MachineState_Aborted))
9676 {
9677 /*
9678 * clear differencing hard disks based on immutable hard disks
9679 * once we've been shut down for any reason
9680 */
9681 rc = wipeOutImmutableDiffs();
9682 }
9683
9684 LogFlowThisFunc (("rc=%08X\n", rc));
9685 LogFlowThisFuncLeave();
9686 return rc;
9687}
9688
9689/**
9690 * Sends the current machine state value to the VM process.
9691 *
9692 * @note Locks this object for reading, then calls a client process.
9693 */
9694HRESULT SessionMachine::updateMachineStateOnClient()
9695{
9696 AutoCaller autoCaller (this);
9697 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
9698
9699 ComPtr <IInternalSessionControl> directControl;
9700 {
9701 AutoReaderLock alock (this);
9702 AssertReturn (!!mData, E_FAIL);
9703 directControl = mData->mSession.mDirectControl;
9704
9705 /* directControl may be already set to NULL here in #OnSessionEnd()
9706 * called too early by the direct session process while there is still
9707 * some operation (like discarding the snapshot) in progress. The client
9708 * process in this case is waiting inside Session::close() for the
9709 * "end session" process object to complete, while #uninit() called by
9710 * #checkForDeath() on the Watcher thread is waiting for the pending
9711 * operation to complete. For now, we accept this inconsitent behavior
9712 * and simply do nothing here. */
9713
9714 if (mData->mSession.mState == SessionState_SessionClosing)
9715 return S_OK;
9716
9717 AssertReturn (!directControl.isNull(), E_FAIL);
9718 }
9719
9720 return directControl->UpdateMachineState (mData->mMachineState);
9721}
9722
9723/* static */
9724DECLCALLBACK(int) SessionMachine::taskHandler (RTTHREAD thread, void *pvUser)
9725{
9726 AssertReturn (pvUser, VERR_INVALID_POINTER);
9727
9728 Task *task = static_cast <Task *> (pvUser);
9729 task->handler();
9730
9731 // it's our responsibility to delete the task
9732 delete task;
9733
9734 return 0;
9735}
9736
9737/////////////////////////////////////////////////////////////////////////////
9738// SnapshotMachine class
9739/////////////////////////////////////////////////////////////////////////////
9740
9741DEFINE_EMPTY_CTOR_DTOR (SnapshotMachine)
9742
9743HRESULT SnapshotMachine::FinalConstruct()
9744{
9745 LogFlowThisFunc (("\n"));
9746
9747 /* set the proper type to indicate we're the SnapshotMachine instance */
9748 unconst (mType) = IsSnapshotMachine;
9749
9750 return S_OK;
9751}
9752
9753void SnapshotMachine::FinalRelease()
9754{
9755 LogFlowThisFunc (("\n"));
9756
9757 uninit();
9758}
9759
9760/**
9761 * Initializes the SnapshotMachine object when taking a snapshot.
9762 *
9763 * @param aSessionMachine machine to take a snapshot from
9764 * @param aSnapshotId snapshot ID of this snapshot machine
9765 * @param aStateFilePath file where the execution state will be later saved
9766 * (or NULL for the offline snapshot)
9767 *
9768 * @note Locks aSessionMachine object for reading.
9769 */
9770HRESULT SnapshotMachine::init (SessionMachine *aSessionMachine,
9771 INPTR GUIDPARAM aSnapshotId,
9772 INPTR BSTR aStateFilePath)
9773{
9774 LogFlowThisFuncEnter();
9775 LogFlowThisFunc (("mName={%ls}\n", aSessionMachine->mUserData->mName.raw()));
9776
9777 AssertReturn (aSessionMachine && !Guid (aSnapshotId).isEmpty(), E_INVALIDARG);
9778
9779 /* Enclose the state transition NotReady->InInit->Ready */
9780 AutoInitSpan autoInitSpan (this);
9781 AssertReturn (autoInitSpan.isOk(), E_UNEXPECTED);
9782
9783 mSnapshotId = aSnapshotId;
9784
9785 AutoReaderLock alock (aSessionMachine);
9786
9787 /* memorize the primary Machine instance (i.e. not SessionMachine!) */
9788 unconst (mPeer) = aSessionMachine->mPeer;
9789 /* share the parent pointer */
9790 unconst (mParent) = mPeer->mParent;
9791
9792 /* take the pointer to Data to share */
9793 mData.share (mPeer->mData);
9794 /*
9795 * take the pointer to UserData to share
9796 * (our UserData must always be the same as Machine's data)
9797 */
9798 mUserData.share (mPeer->mUserData);
9799 /* make a private copy of all other data (recent changes from SessionMachine) */
9800 mHWData.attachCopy (aSessionMachine->mHWData);
9801 mHDData.attachCopy (aSessionMachine->mHDData);
9802
9803 /* SSData is always unique for SnapshotMachine */
9804 mSSData.allocate();
9805 mSSData->mStateFilePath = aStateFilePath;
9806
9807 /*
9808 * create copies of all shared folders (mHWData after attiching a copy
9809 * contains just references to original objects)
9810 */
9811 for (HWData::SharedFolderList::iterator it = mHWData->mSharedFolders.begin();
9812 it != mHWData->mSharedFolders.end();
9813 ++ it)
9814 {
9815 ComObjPtr <SharedFolder> folder;
9816 folder.createObject();
9817 HRESULT rc = folder->initCopy (this, *it);
9818 CheckComRCReturnRC (rc);
9819 *it = folder;
9820 }
9821
9822 /* create all other child objects that will be immutable private copies */
9823
9824 unconst (mBIOSSettings).createObject();
9825 mBIOSSettings->initCopy (this, mPeer->mBIOSSettings);
9826
9827#ifdef VBOX_VRDP
9828 unconst (mVRDPServer).createObject();
9829 mVRDPServer->initCopy (this, mPeer->mVRDPServer);
9830#endif
9831
9832 unconst (mDVDDrive).createObject();
9833 mDVDDrive->initCopy (this, mPeer->mDVDDrive);
9834
9835 unconst (mFloppyDrive).createObject();
9836 mFloppyDrive->initCopy (this, mPeer->mFloppyDrive);
9837
9838 unconst (mAudioAdapter).createObject();
9839 mAudioAdapter->initCopy (this, mPeer->mAudioAdapter);
9840
9841 unconst (mUSBController).createObject();
9842 mUSBController->initCopy (this, mPeer->mUSBController);
9843
9844 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
9845 {
9846 unconst (mNetworkAdapters [slot]).createObject();
9847 mNetworkAdapters [slot]->initCopy (this, mPeer->mNetworkAdapters [slot]);
9848 }
9849
9850 /* Confirm a successful initialization when it's the case */
9851 autoInitSpan.setSucceeded();
9852
9853 LogFlowThisFuncLeave();
9854 return S_OK;
9855}
9856
9857/**
9858 * Initializes the SnapshotMachine object when loading from the settings file.
9859 *
9860 * @param aMachine machine the snapshot belngs to
9861 * @param aHWNode <Hardware> node
9862 * @param aHDAsNode <HardDiskAttachments> node
9863 * @param aSnapshotId snapshot ID of this snapshot machine
9864 * @param aStateFilePath file where the execution state is saved
9865 * (or NULL for the offline snapshot)
9866 *
9867 * @note Locks aMachine object for reading.
9868 */
9869HRESULT SnapshotMachine::init (Machine *aMachine, CFGNODE aHWNode, CFGNODE aHDAsNode,
9870 INPTR GUIDPARAM aSnapshotId, INPTR BSTR aStateFilePath)
9871{
9872 LogFlowThisFuncEnter();
9873 LogFlowThisFunc (("mName={%ls}\n", aMachine->mUserData->mName.raw()));
9874
9875 AssertReturn (aMachine && aHWNode && aHDAsNode && !Guid (aSnapshotId).isEmpty(),
9876 E_INVALIDARG);
9877
9878 /* Enclose the state transition NotReady->InInit->Ready */
9879 AutoInitSpan autoInitSpan (this);
9880 AssertReturn (autoInitSpan.isOk(), E_UNEXPECTED);
9881
9882 mSnapshotId = aSnapshotId;
9883
9884 AutoReaderLock alock (aMachine);
9885
9886 /* memorize the primary Machine instance */
9887 unconst (mPeer) = aMachine;
9888 /* share the parent pointer */
9889 unconst (mParent) = mPeer->mParent;
9890
9891 /* take the pointer to Data to share */
9892 mData.share (mPeer->mData);
9893 /*
9894 * take the pointer to UserData to share
9895 * (our UserData must always be the same as Machine's data)
9896 */
9897 mUserData.share (mPeer->mUserData);
9898 /* allocate private copies of all other data (will be loaded from settings) */
9899 mHWData.allocate();
9900 mHDData.allocate();
9901
9902 /* SSData is always unique for SnapshotMachine */
9903 mSSData.allocate();
9904 mSSData->mStateFilePath = aStateFilePath;
9905
9906 /* create all other child objects that will be immutable private copies */
9907
9908 unconst (mBIOSSettings).createObject();
9909 mBIOSSettings->init (this);
9910
9911#ifdef VBOX_VRDP
9912 unconst (mVRDPServer).createObject();
9913 mVRDPServer->init (this);
9914#endif
9915
9916 unconst (mDVDDrive).createObject();
9917 mDVDDrive->init (this);
9918
9919 unconst (mFloppyDrive).createObject();
9920 mFloppyDrive->init (this);
9921
9922 unconst (mAudioAdapter).createObject();
9923 mAudioAdapter->init (this);
9924
9925 unconst (mUSBController).createObject();
9926 mUSBController->init (this);
9927
9928 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
9929 {
9930 unconst (mNetworkAdapters [slot]).createObject();
9931 mNetworkAdapters [slot]->init (this, slot);
9932 }
9933
9934 /* load hardware and harddisk settings */
9935
9936 HRESULT rc = loadHardware (aHWNode);
9937 if (SUCCEEDED (rc))
9938 rc = loadHardDisks (aHDAsNode, true /* aRegistered */, &mSnapshotId);
9939
9940 if (SUCCEEDED (rc))
9941 {
9942 /* commit all changes made during the initialization */
9943 commit();
9944 }
9945
9946 /* Confirm a successful initialization when it's the case */
9947 if (SUCCEEDED (rc))
9948 autoInitSpan.setSucceeded();
9949
9950 LogFlowThisFuncLeave();
9951 return rc;
9952}
9953
9954/**
9955 * Uninitializes this SnapshotMachine object.
9956 */
9957void SnapshotMachine::uninit()
9958{
9959 LogFlowThisFuncEnter();
9960
9961 /* Enclose the state transition Ready->InUninit->NotReady */
9962 AutoUninitSpan autoUninitSpan (this);
9963 if (autoUninitSpan.uninitDone())
9964 return;
9965
9966 uninitDataAndChildObjects();
9967
9968 unconst (mParent).setNull();
9969 unconst (mPeer).setNull();
9970
9971 LogFlowThisFuncLeave();
9972}
9973
9974// AutoLock::Lockable interface
9975////////////////////////////////////////////////////////////////////////////////
9976
9977/**
9978 * Overrides VirtualBoxBase::lockHandle() in order to share the lock handle
9979 * with the primary Machine instance (mPeer).
9980 */
9981AutoLock::Handle *SnapshotMachine::lockHandle() const
9982{
9983 AssertReturn (!mPeer.isNull(), NULL);
9984 return mPeer->lockHandle();
9985}
9986
9987// public methods only for internal purposes
9988////////////////////////////////////////////////////////////////////////////////
9989
9990/**
9991 * Called by the snapshot object associated with this SnapshotMachine when
9992 * snapshot data such as name or description is changed.
9993 *
9994 * @note Locks this object for writing.
9995 */
9996HRESULT SnapshotMachine::onSnapshotChange (Snapshot *aSnapshot)
9997{
9998 AutoLock alock (this);
9999
10000 mPeer->saveSnapshotSettings (aSnapshot, SaveSS_UpdateAttrsOp);
10001
10002 /* inform callbacks */
10003 mParent->onSnapshotChange (mData->mUuid, aSnapshot->data().mId);
10004
10005 return S_OK;
10006}
Note: See TracBrowser for help on using the repository browser.

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