VirtualBox

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

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

Moved the template code out of cdefs.h, partly because it didn't belong there but mostly because it was at the end of the file and would screw up any attempts made by the object cache at avoid recompiling on cdefs.h changes.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 316.8 KB
Line 
1/** @file
2 *
3 * VirtualBox COM class implementation
4 */
5
6/*
7 * Copyright (C) 2006-2007 innotek GmbH
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.215389.xyz. 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_ClipDisabled;
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 AssertComRCReturn (autoCaller.rc(), 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 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8590
8591 ComPtr <IInternalSessionControl> directControl;
8592 {
8593 AutoReaderLock alock (this);
8594 directControl = mData->mSession.mDirectControl;
8595 }
8596
8597 /* ignore notifications sent after #OnSessionEnd() is called */
8598 if (!directControl)
8599 return S_OK;
8600
8601 return directControl->OnUSBDeviceAttach (aDevice, aError);
8602}
8603
8604/**
8605 * @note Locks this object for reading.
8606 */
8607HRESULT SessionMachine::onUSBDeviceDetach (INPTR GUIDPARAM aId,
8608 IVirtualBoxErrorInfo *aError)
8609{
8610 LogFlowThisFunc (("\n"));
8611
8612 AutoCaller autoCaller (this);
8613 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8614
8615 ComPtr <IInternalSessionControl> directControl;
8616 {
8617 AutoReaderLock alock (this);
8618 directControl = mData->mSession.mDirectControl;
8619 }
8620
8621 /* ignore notifications sent after #OnSessionEnd() is called */
8622 if (!directControl)
8623 return S_OK;
8624
8625 return directControl->OnUSBDeviceDetach (aId, aError);
8626}
8627
8628// protected methods
8629/////////////////////////////////////////////////////////////////////////////
8630
8631/**
8632 * Helper method to finalize saving the state.
8633 *
8634 * @note Must be called from under this object's lock.
8635 *
8636 * @param aSuccess TRUE if the snapshot has been taken successfully
8637 *
8638 * @note Locks mParent + this objects for writing.
8639 */
8640HRESULT SessionMachine::endSavingState (BOOL aSuccess)
8641{
8642 LogFlowThisFuncEnter();
8643
8644 AutoCaller autoCaller (this);
8645 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8646
8647 /* mParent->removeProgress() needs mParent lock */
8648 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
8649
8650 HRESULT rc = S_OK;
8651
8652 if (aSuccess)
8653 {
8654 mSSData->mStateFilePath = mSnapshotData.mStateFilePath;
8655
8656 /* save all VM settings */
8657 rc = saveSettings();
8658 }
8659 else
8660 {
8661 /* delete the saved state file (it might have been already created) */
8662 RTFileDelete (Utf8Str (mSnapshotData.mStateFilePath));
8663 }
8664
8665 /* remove the completed progress object */
8666 mParent->removeProgress (mSnapshotData.mProgressId);
8667
8668 /* clear out the temporary saved state data */
8669 mSnapshotData.mLastState = MachineState_InvalidMachineState;
8670 mSnapshotData.mProgressId.clear();
8671 mSnapshotData.mStateFilePath.setNull();
8672
8673 LogFlowThisFuncLeave();
8674 return rc;
8675}
8676
8677/**
8678 * Helper method to finalize taking a snapshot.
8679 * Gets called only from #EndTakingSnapshot() that is expected to
8680 * be called by the VM process when it finishes *all* the tasks related to
8681 * taking a snapshot, either scucessfully or unsuccessfilly.
8682 *
8683 * @param aSuccess TRUE if the snapshot has been taken successfully
8684 *
8685 * @note Locks mParent + this objects for writing.
8686 */
8687HRESULT SessionMachine::endTakingSnapshot (BOOL aSuccess)
8688{
8689 LogFlowThisFuncEnter();
8690
8691 AutoCaller autoCaller (this);
8692 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8693
8694 /* Progress object uninitialization needs mParent lock */
8695 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
8696
8697 HRESULT rc = S_OK;
8698
8699 if (aSuccess)
8700 {
8701 /* the server progress must be completed on success */
8702 Assert (mSnapshotData.mServerProgress->completed());
8703
8704 mData->mCurrentSnapshot = mSnapshotData.mSnapshot;
8705 /* memorize the first snapshot if necessary */
8706 if (!mData->mFirstSnapshot)
8707 mData->mFirstSnapshot = mData->mCurrentSnapshot;
8708
8709 int opFlags = SaveSS_AddOp | SaveSS_UpdateCurrentId;
8710 if (mSnapshotData.mLastState != MachineState_Paused && !isModified())
8711 {
8712 /*
8713 * the machine was powered off or saved when taking a snapshot,
8714 * so reset the mCurrentStateModified flag
8715 */
8716 mData->mCurrentStateModified = FALSE;
8717 opFlags |= SaveSS_UpdateCurStateModified;
8718 }
8719
8720 rc = saveSnapshotSettings (mSnapshotData.mSnapshot, opFlags);
8721 }
8722
8723 if (!aSuccess || FAILED (rc))
8724 {
8725 if (mSnapshotData.mSnapshot)
8726 {
8727 /* wait for the completion of the server progress (diff VDI creation) */
8728 /// @todo (dmik) later, we will definitely want to cancel it instead
8729 // (when the cancel function is implemented)
8730 mSnapshotData.mServerProgress->WaitForCompletion (-1);
8731
8732 /*
8733 * delete all differencing VDIs created
8734 * (this will attach their parents back)
8735 */
8736 rc = deleteSnapshotDiffs (mSnapshotData.mSnapshot);
8737 /* continue cleanup on error */
8738
8739 /* delete the saved state file (it might have been already created) */
8740 if (mSnapshotData.mSnapshot->stateFilePath())
8741 RTFileDelete (Utf8Str (mSnapshotData.mSnapshot->stateFilePath()));
8742
8743 mSnapshotData.mSnapshot->uninit();
8744 }
8745 }
8746
8747 /* inform callbacks */
8748 if (aSuccess && SUCCEEDED (rc))
8749 mParent->onSnapshotTaken (mData->mUuid, mSnapshotData.mSnapshot->data().mId);
8750
8751 /* clear out the snapshot data */
8752 mSnapshotData.mLastState = MachineState_InvalidMachineState;
8753 mSnapshotData.mSnapshot.setNull();
8754 mSnapshotData.mServerProgress.setNull();
8755 /* uninitialize the combined progress (to remove it from the VBox collection) */
8756 if (!mSnapshotData.mCombinedProgress.isNull())
8757 {
8758 mSnapshotData.mCombinedProgress->uninit();
8759 mSnapshotData.mCombinedProgress.setNull();
8760 }
8761
8762 LogFlowThisFuncLeave();
8763 return rc;
8764}
8765
8766/**
8767 * Take snapshot task handler.
8768 * Must be called only by TakeSnapshotTask::handler()!
8769 *
8770 * The sole purpose of this task is to asynchronously create differencing VDIs
8771 * and copy the saved state file (when necessary). The VM process will wait
8772 * for this task to complete using the mSnapshotData.mServerProgress
8773 * returned to it.
8774 *
8775 * @note Locks mParent + this objects for writing.
8776 */
8777void SessionMachine::takeSnapshotHandler (TakeSnapshotTask &aTask)
8778{
8779 LogFlowThisFuncEnter();
8780
8781 AutoCaller autoCaller (this);
8782
8783 LogFlowThisFunc (("state=%d\n", autoCaller.state()));
8784 if (!autoCaller.isOk())
8785 {
8786 /*
8787 * we might have been uninitialized because the session was
8788 * accidentally closed by the client, so don't assert
8789 */
8790 LogFlowThisFuncLeave();
8791 return;
8792 }
8793
8794 /* endTakingSnapshot() needs mParent lock */
8795 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
8796
8797 HRESULT rc = S_OK;
8798
8799 LogFlowThisFunc (("Creating differencing VDIs...\n"));
8800
8801 /* create new differencing hard disks and attach them to this machine */
8802 rc = createSnapshotDiffs (&mSnapshotData.mSnapshot->data().mId,
8803 mUserData->mSnapshotFolderFull,
8804 mSnapshotData.mServerProgress,
8805 true /* aOnline */);
8806
8807 if (SUCCEEDED (rc) && mSnapshotData.mLastState == MachineState_Saved)
8808 {
8809 Utf8Str stateFrom = mSSData->mStateFilePath;
8810 Utf8Str stateTo = mSnapshotData.mSnapshot->stateFilePath();
8811
8812 LogFlowThisFunc (("Copying the execution state from '%s' to '%s'...\n",
8813 stateFrom.raw(), stateTo.raw()));
8814
8815 mSnapshotData.mServerProgress->advanceOperation (
8816 Bstr (tr ("Copying the execution state")));
8817
8818 /*
8819 * We can safely leave the lock here:
8820 * mMachineState is MachineState_Saving here
8821 */
8822 alock.leave();
8823
8824 /* copy the state file */
8825 int vrc = RTFileCopyEx (stateFrom, stateTo, progressCallback,
8826 static_cast <Progress *> (mSnapshotData.mServerProgress));
8827
8828 alock.enter();
8829
8830 if (VBOX_FAILURE (vrc))
8831 rc = setError (E_FAIL,
8832 tr ("Could not copy the state file '%ls' to '%ls' (%Vrc)"),
8833 stateFrom.raw(), stateTo.raw());
8834 }
8835
8836 /*
8837 * we have to call endTakingSnapshot() here if the snapshot was taken
8838 * offline, because the VM process will not do it in this case
8839 */
8840 if (mSnapshotData.mLastState != MachineState_Paused)
8841 {
8842 LogFlowThisFunc (("Finalizing the taken snapshot (rc=%08X)...\n", rc));
8843
8844 setMachineState (mSnapshotData.mLastState);
8845 updateMachineStateOnClient();
8846
8847 /* finalize the progress after setting the state, for consistency */
8848 mSnapshotData.mServerProgress->notifyComplete (rc);
8849
8850 endTakingSnapshot (SUCCEEDED (rc));
8851 }
8852 else
8853 {
8854 mSnapshotData.mServerProgress->notifyComplete (rc);
8855 }
8856
8857 LogFlowThisFuncLeave();
8858}
8859
8860/**
8861 * Discard snapshot task handler.
8862 * Must be called only by DiscardSnapshotTask::handler()!
8863 *
8864 * When aTask.subTask is true, the associated progress object is left
8865 * uncompleted on success. On failure, the progress is marked as completed
8866 * regardless of this parameter.
8867 *
8868 * @note Locks mParent + this + child objects for writing!
8869 */
8870void SessionMachine::discardSnapshotHandler (DiscardSnapshotTask &aTask)
8871{
8872 LogFlowThisFuncEnter();
8873
8874 AutoCaller autoCaller (this);
8875
8876 LogFlowThisFunc (("state=%d\n", autoCaller.state()));
8877 if (!autoCaller.isOk())
8878 {
8879 /*
8880 * we might have been uninitialized because the session was
8881 * accidentally closed by the client, so don't assert
8882 */
8883 aTask.progress->notifyComplete (
8884 E_FAIL, COM_IIDOF (IMachine), getComponentName(),
8885 tr ("The session has been accidentally closed"));
8886
8887 LogFlowThisFuncLeave();
8888 return;
8889 }
8890
8891 ComObjPtr <SnapshotMachine> sm = aTask.snapshot->data().mMachine;
8892
8893 /* mParent is locked because of Progress::notifyComplete(), etc. */
8894 AutoMultiLock <3> alock (mParent->wlock(), this->wlock(), sm->rlock());
8895
8896 /* Safe locking in the direction parent->child */
8897 AutoLock snapshotLock (aTask.snapshot);
8898 AutoLock snapshotChildrenLock (aTask.snapshot->childrenLock());
8899
8900 HRESULT rc = S_OK;
8901
8902 /* save the snapshot ID (for callbacks) */
8903 Guid snapshotId = aTask.snapshot->data().mId;
8904
8905 do
8906 {
8907 /* first pass: */
8908 LogFlowThisFunc (("Check hard disk accessibility and affected machines...\n"));
8909
8910 HDData::HDAttachmentList::const_iterator it;
8911 for (it = sm->mHDData->mHDAttachments.begin();
8912 it != sm->mHDData->mHDAttachments.end();
8913 ++ it)
8914 {
8915 ComObjPtr <HardDiskAttachment> hda = *it;
8916 ComObjPtr <HardDisk> hd = hda->hardDisk();
8917 ComObjPtr <HardDisk> parent = hd->parent();
8918
8919 AutoLock hdLock (hd);
8920
8921 if (hd->hasForeignChildren())
8922 {
8923 rc = setError (E_FAIL,
8924 tr ("One or more hard disks belonging to other machines are "
8925 "based on the hard disk '%ls' stored in the snapshot '%ls'"),
8926 hd->toString().raw(), aTask.snapshot->data().mName.raw());
8927 break;
8928 }
8929
8930 if (hd->type() == HardDiskType_NormalHardDisk)
8931 {
8932 AutoLock hdChildrenLock (hd->childrenLock());
8933 size_t childrenCount = hd->children().size();
8934 if (childrenCount > 1)
8935 {
8936 rc = setError (E_FAIL,
8937 tr ("Normal hard disk '%ls' stored in the snapshot '%ls' "
8938 "has more than one child hard disk (%d)"),
8939 hd->toString().raw(), aTask.snapshot->data().mName.raw(),
8940 childrenCount);
8941 break;
8942 }
8943 }
8944 else
8945 {
8946 ComAssertMsgFailedBreak (("Invalid hard disk type %d\n", hd->type()),
8947 rc = E_FAIL);
8948 }
8949
8950 Bstr accessError;
8951 rc = hd->getAccessibleWithChildren (accessError);
8952 CheckComRCBreakRC (rc);
8953
8954 if (!accessError.isNull())
8955 {
8956 rc = setError (E_FAIL,
8957 tr ("Hard disk '%ls' stored in the snapshot '%ls' is not "
8958 "accessible (%ls)"),
8959 hd->toString().raw(), aTask.snapshot->data().mName.raw(),
8960 accessError.raw());
8961 break;
8962 }
8963
8964 rc = hd->setBusyWithChildren();
8965 if (FAILED (rc))
8966 {
8967 /* reset the busy flag of all previous hard disks */
8968 while (it != sm->mHDData->mHDAttachments.begin())
8969 (*(-- it))->hardDisk()->clearBusyWithChildren();
8970 break;
8971 }
8972 }
8973
8974 CheckComRCBreakRC (rc);
8975
8976 /* second pass: */
8977 LogFlowThisFunc (("Performing actual vdi merging...\n"));
8978
8979 for (it = sm->mHDData->mHDAttachments.begin();
8980 it != sm->mHDData->mHDAttachments.end();
8981 ++ it)
8982 {
8983 ComObjPtr <HardDiskAttachment> hda = *it;
8984 ComObjPtr <HardDisk> hd = hda->hardDisk();
8985 ComObjPtr <HardDisk> parent = hd->parent();
8986
8987 AutoLock hdLock (hd);
8988
8989 Bstr hdRootString = hd->root()->toString (true /* aShort */);
8990
8991 if (parent)
8992 {
8993 if (hd->isParentImmutable())
8994 {
8995 aTask.progress->advanceOperation (Bstr (Utf8StrFmt (
8996 tr ("Discarding changes to immutable hard disk '%ls'"),
8997 hdRootString.raw())));
8998
8999 /* clear the busy flag before unregistering */
9000 hd->clearBusy();
9001
9002 /*
9003 * unregisterDiffHardDisk() is supposed to delete and uninit
9004 * the differencing hard disk
9005 */
9006 rc = mParent->unregisterDiffHardDisk (hd);
9007 CheckComRCBreakRC (rc);
9008 continue;
9009 }
9010 else
9011 {
9012 /*
9013 * differencing VDI:
9014 * merge this image to all its children
9015 */
9016
9017 aTask.progress->advanceOperation (Bstr (Utf8StrFmt (
9018 tr ("Merging changes to normal hard disk '%ls' to children"),
9019 hdRootString.raw())));
9020
9021 snapshotChildrenLock.unlock();
9022 snapshotLock.unlock();
9023 alock.leave();
9024
9025 rc = hd->asVDI()->mergeImageToChildren (aTask.progress);
9026
9027 alock.enter();
9028 snapshotLock.lock();
9029 snapshotChildrenLock.lock();
9030
9031 // debug code
9032 // if (it != sm->mHDData->mHDAttachments.begin())
9033 // {
9034 // rc = setError (E_FAIL, "Simulated failure");
9035 // break;
9036 //}
9037
9038 if (SUCCEEDED (rc))
9039 rc = mParent->unregisterDiffHardDisk (hd);
9040 else
9041 hd->clearBusyWithChildren();
9042
9043 CheckComRCBreakRC (rc);
9044 }
9045 }
9046 else if (hd->type() == HardDiskType_NormalHardDisk)
9047 {
9048 /*
9049 * normal vdi has the only child or none
9050 * (checked in the first pass)
9051 */
9052
9053 ComObjPtr <HardDisk> child;
9054 {
9055 AutoLock hdChildrenLock (hd->childrenLock());
9056 if (hd->children().size())
9057 child = hd->children().front();
9058 }
9059
9060 if (child.isNull())
9061 {
9062 aTask.progress->advanceOperation (Bstr (Utf8StrFmt (
9063 tr ("Detaching normal hard disk '%ls'"),
9064 hdRootString.raw())));
9065
9066 /* just deassociate the normal image from this machine */
9067 hd->setMachineId (Guid());
9068 hd->setSnapshotId (Guid());
9069
9070 /* clear the busy flag */
9071 hd->clearBusy();
9072 }
9073 else
9074 {
9075 AutoLock childLock (child);
9076
9077 aTask.progress->advanceOperation (Bstr (Utf8StrFmt (
9078 tr ("Preserving changes to normal hard disk '%ls'"),
9079 hdRootString.raw())));
9080
9081 ComObjPtr <Machine> cm;
9082 ComObjPtr <Snapshot> cs;
9083 ComObjPtr <HardDiskAttachment> childHda;
9084 rc = findHardDiskAttachment (child, &cm, &cs, &childHda);
9085 CheckComRCBreakRC (rc);
9086 /* must be the same machine (checked in the first pass) */
9087 ComAssertBreak (cm->mData->mUuid == mData->mUuid, rc = E_FAIL);
9088
9089 /* merge the child to this basic image */
9090
9091 snapshotChildrenLock.unlock();
9092 snapshotLock.unlock();
9093 alock.leave();
9094
9095 rc = child->asVDI()->mergeImageToParent (aTask.progress);
9096
9097 alock.enter();
9098 snapshotLock.lock();
9099 snapshotChildrenLock.lock();
9100
9101 if (SUCCEEDED (rc))
9102 rc = mParent->unregisterDiffHardDisk (child);
9103 else
9104 hd->clearBusyWithChildren();
9105
9106 CheckComRCBreakRC (rc);
9107
9108 /* reset the snapshot Id */
9109 hd->setSnapshotId (Guid());
9110
9111 /* replace the child image in the appropriate place */
9112 childHda->updateHardDisk (hd, FALSE /* aDirty */);
9113
9114 if (!cs)
9115 {
9116 aTask.settingsChanged = true;
9117 }
9118 else
9119 {
9120 rc = cm->saveSnapshotSettings (cs, SaveSS_UpdateAllOp);
9121 CheckComRCBreakRC (rc);
9122 }
9123 }
9124 }
9125 else
9126 {
9127 ComAssertMsgFailedBreak (("Invalid hard disk type %d\n", hd->type()),
9128 rc = E_FAIL);
9129 }
9130 }
9131
9132 /* preserve existing error info */
9133 ErrorInfoKeeper mergeEik;
9134 HRESULT mergeRc = rc;
9135
9136 if (FAILED (rc))
9137 {
9138 /* clear the busy flag on the rest of hard disks */
9139 for (++ it; it != sm->mHDData->mHDAttachments.end(); ++ it)
9140 (*it)->hardDisk()->clearBusyWithChildren();
9141 }
9142
9143 /*
9144 * we have to try to discard the snapshot even if merging failed
9145 * because some images might have been already merged (and deleted)
9146 */
9147
9148 do
9149 {
9150 LogFlowThisFunc (("Discarding the snapshot (reparenting children)...\n"));
9151
9152 ComObjPtr <Snapshot> parentSnapshot = aTask.snapshot->parent();
9153
9154 /// @todo (dmik):
9155 // when we introduce clones later, discarding the snapshot
9156 // will affect the current and first snapshots of clones, if they are
9157 // direct children of this snapshot. So we will need to lock machines
9158 // associated with child snapshots as well and update mCurrentSnapshot
9159 // and/or mFirstSnapshot fields.
9160
9161 if (aTask.snapshot == mData->mCurrentSnapshot)
9162 {
9163 /* currently, the parent snapshot must refer to the same machine */
9164 ComAssertBreak (
9165 !parentSnapshot ||
9166 parentSnapshot->data().mMachine->mData->mUuid == mData->mUuid,
9167 rc = E_FAIL);
9168 mData->mCurrentSnapshot = parentSnapshot;
9169 /* mark the current state as modified */
9170 mData->mCurrentStateModified = TRUE;
9171 }
9172
9173 if (aTask.snapshot == mData->mFirstSnapshot)
9174 {
9175 /*
9176 * the first snapshot must have only one child when discarded,
9177 * or no children at all
9178 */
9179 ComAssertBreak (aTask.snapshot->children().size() <= 1, rc = E_FAIL);
9180
9181 if (aTask.snapshot->children().size() == 1)
9182 {
9183 ComObjPtr <Snapshot> childSnapshot = aTask.snapshot->children().front();
9184 ComAssertBreak (
9185 childSnapshot->data().mMachine->mData->mUuid == mData->mUuid,
9186 rc = E_FAIL);
9187 mData->mFirstSnapshot = childSnapshot;
9188 }
9189 else
9190 mData->mFirstSnapshot.setNull();
9191 }
9192
9193 /// @todo (dmik)
9194 // if we implement some warning mechanism later, we'll have
9195 // to return a warning if the state file path cannot be deleted
9196 Bstr stateFilePath = aTask.snapshot->stateFilePath();
9197 if (stateFilePath)
9198 RTFileDelete (Utf8Str (stateFilePath));
9199
9200 aTask.snapshot->discard();
9201
9202 rc = saveSnapshotSettings (parentSnapshot,
9203 SaveSS_UpdateAllOp | SaveSS_UpdateCurrentId);
9204 }
9205 while (0);
9206
9207 /* restore the merge error if any (ErrorInfo will be restored
9208 * automatically) */
9209 if (FAILED (mergeRc))
9210 rc = mergeRc;
9211 }
9212 while (0);
9213
9214 if (!aTask.subTask || FAILED (rc))
9215 {
9216 if (!aTask.subTask)
9217 {
9218 /* preserve existing error info */
9219 ErrorInfoKeeper eik;
9220
9221 /* restore the machine state */
9222 setMachineState (aTask.state);
9223 updateMachineStateOnClient();
9224
9225 /*
9226 * save settings anyway, since we've already changed the current
9227 * machine configuration
9228 */
9229 if (aTask.settingsChanged)
9230 {
9231 saveSettings (true /* aMarkCurStateAsModified */,
9232 true /* aInformCallbacksAnyway */);
9233 }
9234 }
9235
9236 /* set the result (this will try to fetch current error info on failure) */
9237 aTask.progress->notifyComplete (rc);
9238 }
9239
9240 if (SUCCEEDED (rc))
9241 mParent->onSnapshotDiscarded (mData->mUuid, snapshotId);
9242
9243 LogFlowThisFunc (("Done discarding snapshot (rc=%08X)\n", rc));
9244 LogFlowThisFuncLeave();
9245}
9246
9247/**
9248 * Discard current state task handler.
9249 * Must be called only by DiscardCurrentStateTask::handler()!
9250 *
9251 * @note Locks mParent + this object for writing.
9252 */
9253void SessionMachine::discardCurrentStateHandler (DiscardCurrentStateTask &aTask)
9254{
9255 LogFlowThisFuncEnter();
9256
9257 AutoCaller autoCaller (this);
9258
9259 LogFlowThisFunc (("state=%d\n", autoCaller.state()));
9260 if (!autoCaller.isOk())
9261 {
9262 /*
9263 * we might have been uninitialized because the session was
9264 * accidentally closed by the client, so don't assert
9265 */
9266 aTask.progress->notifyComplete (
9267 E_FAIL, COM_IIDOF (IMachine), getComponentName(),
9268 tr ("The session has been accidentally closed"));
9269
9270 LogFlowThisFuncLeave();
9271 return;
9272 }
9273
9274 /* mParent is locked because of Progress::notifyComplete(), etc. */
9275 AutoMultiLock <2> alock (mParent->wlock(), this->wlock());
9276
9277 /*
9278 * discard all current changes to mUserData (name, OSType etc.)
9279 * (note that the machine is powered off, so there is no need
9280 * to inform the direct session)
9281 */
9282 if (isModified())
9283 rollback (false /* aNotify */);
9284
9285 HRESULT rc = S_OK;
9286
9287 bool errorInSubtask = false;
9288 bool stateRestored = false;
9289
9290 const bool isLastSnapshot = mData->mCurrentSnapshot->parent().isNull();
9291
9292 do
9293 {
9294 /*
9295 * discard the saved state file if the machine was Saved prior
9296 * to this operation
9297 */
9298 if (aTask.state == MachineState_Saved)
9299 {
9300 Assert (!mSSData->mStateFilePath.isEmpty());
9301 RTFileDelete (Utf8Str (mSSData->mStateFilePath));
9302 mSSData->mStateFilePath.setNull();
9303 aTask.modifyLastState (MachineState_PoweredOff);
9304 rc = saveStateSettings (SaveSTS_StateFilePath);
9305 CheckComRCBreakRC (rc);
9306 }
9307
9308 if (aTask.discardCurrentSnapshot && !isLastSnapshot)
9309 {
9310 /*
9311 * the "discard current snapshot and state" task is in action,
9312 * the current snapshot is not the last one.
9313 * Discard the current snapshot first.
9314 */
9315
9316 DiscardSnapshotTask subTask (aTask, mData->mCurrentSnapshot);
9317 subTask.subTask = true;
9318 discardSnapshotHandler (subTask);
9319 aTask.settingsChanged = subTask.settingsChanged;
9320 if (aTask.progress->completed())
9321 {
9322 /*
9323 * the progress can be completed by a subtask only if there was
9324 * a failure
9325 */
9326 Assert (FAILED (aTask.progress->resultCode()));
9327 errorInSubtask = true;
9328 rc = aTask.progress->resultCode();
9329 break;
9330 }
9331 }
9332
9333 LONG64 snapshotTimeStamp = 0;
9334
9335 {
9336 ComObjPtr <Snapshot> curSnapshot = mData->mCurrentSnapshot;
9337 AutoLock snapshotLock (curSnapshot);
9338
9339 /* remember the timestamp of the snapshot we're restoring from */
9340 snapshotTimeStamp = curSnapshot->data().mTimeStamp;
9341
9342 /* copy all hardware data from the current snapshot */
9343 copyFrom (curSnapshot->data().mMachine);
9344
9345 LogFlowThisFunc (("Restoring VDIs from the snapshot...\n"));
9346
9347 /* restore the attachmends from the snapshot */
9348 mHDData.backup();
9349 mHDData->mHDAttachments =
9350 curSnapshot->data().mMachine->mHDData->mHDAttachments;
9351
9352 snapshotLock.unlock();
9353 alock.leave();
9354 rc = createSnapshotDiffs (NULL, mUserData->mSnapshotFolderFull,
9355 aTask.progress,
9356 false /* aOnline */);
9357 alock.enter();
9358 snapshotLock.lock();
9359
9360 if (FAILED (rc))
9361 {
9362 /* here we can still safely rollback, so do it */
9363 /* preserve existing error info */
9364 ErrorInfoKeeper eik;
9365 /* undo all changes */
9366 rollback (false /* aNotify */);
9367 break;
9368 }
9369
9370 /*
9371 * note: old VDIs will be deassociated/deleted on #commit() called
9372 * either from #saveSettings() or directly at the end
9373 */
9374
9375 /* should not have a saved state file associated at this point */
9376 Assert (mSSData->mStateFilePath.isNull());
9377
9378 if (curSnapshot->stateFilePath())
9379 {
9380 Utf8Str snapStateFilePath = curSnapshot->stateFilePath();
9381
9382 Utf8Str stateFilePath = Utf8StrFmt ("%ls%c{%Vuuid}.sav",
9383 mUserData->mSnapshotFolderFull.raw(),
9384 RTPATH_DELIMITER, mData->mUuid.raw());
9385
9386 LogFlowThisFunc (("Copying saved state file from '%s' to '%s'...\n",
9387 snapStateFilePath.raw(), stateFilePath.raw()));
9388
9389 aTask.progress->advanceOperation (
9390 Bstr (tr ("Restoring the execution state")));
9391
9392 /* copy the state file */
9393 snapshotLock.unlock();
9394 alock.leave();
9395 int vrc = RTFileCopyEx (snapStateFilePath, stateFilePath,
9396 progressCallback, aTask.progress);
9397 alock.enter();
9398 snapshotLock.lock();
9399
9400 if (VBOX_SUCCESS (vrc))
9401 {
9402 mSSData->mStateFilePath = stateFilePath;
9403 }
9404 else
9405 {
9406 rc = setError (E_FAIL,
9407 tr ("Could not copy the state file '%s' to '%s' (%Vrc)"),
9408 snapStateFilePath.raw(), stateFilePath.raw(), vrc);
9409 break;
9410 }
9411 }
9412 }
9413
9414 bool informCallbacks = false;
9415
9416 if (aTask.discardCurrentSnapshot && isLastSnapshot)
9417 {
9418 /*
9419 * discard the current snapshot and state task is in action,
9420 * the current snapshot is the last one.
9421 * Discard the current snapshot after discarding the current state.
9422 */
9423
9424 /* commit changes to fixup hard disks before discarding */
9425 rc = commit();
9426 if (SUCCEEDED (rc))
9427 {
9428 DiscardSnapshotTask subTask (aTask, mData->mCurrentSnapshot);
9429 subTask.subTask = true;
9430 discardSnapshotHandler (subTask);
9431 aTask.settingsChanged = subTask.settingsChanged;
9432 if (aTask.progress->completed())
9433 {
9434 /*
9435 * the progress can be completed by a subtask only if there
9436 * was a failure
9437 */
9438 Assert (FAILED (aTask.progress->resultCode()));
9439 errorInSubtask = true;
9440 rc = aTask.progress->resultCode();
9441 }
9442 }
9443
9444 /*
9445 * we've committed already, so inform callbacks anyway to ensure
9446 * they don't miss some change
9447 */
9448 informCallbacks = true;
9449 }
9450
9451 /*
9452 * we have already discarded the current state, so set the
9453 * execution state accordingly no matter of the discard snapshot result
9454 */
9455 if (mSSData->mStateFilePath)
9456 setMachineState (MachineState_Saved);
9457 else
9458 setMachineState (MachineState_PoweredOff);
9459
9460 updateMachineStateOnClient();
9461 stateRestored = true;
9462
9463 if (errorInSubtask)
9464 break;
9465
9466 /* assign the timestamp from the snapshot */
9467 Assert (snapshotTimeStamp != 0);
9468 mData->mLastStateChange = snapshotTimeStamp;
9469
9470 /* mark the current state as not modified */
9471 mData->mCurrentStateModified = FALSE;
9472
9473 /* save all settings and commit */
9474 rc = saveSettings (false /* aMarkCurStateAsModified */,
9475 informCallbacks);
9476 aTask.settingsChanged = false;
9477 }
9478 while (0);
9479
9480 if (FAILED (rc))
9481 {
9482 /* preserve existing error info */
9483 ErrorInfoKeeper eik;
9484
9485 if (!stateRestored)
9486 {
9487 /* restore the machine state */
9488 setMachineState (aTask.state);
9489 updateMachineStateOnClient();
9490 }
9491
9492 /*
9493 * save all settings and commit if still modified (there is no way to
9494 * rollback properly). Note that isModified() will return true after
9495 * copyFrom(). Also save the settings if requested by the subtask.
9496 */
9497 if (isModified() || aTask.settingsChanged)
9498 {
9499 if (aTask.settingsChanged)
9500 saveSettings (true /* aMarkCurStateAsModified */,
9501 true /* aInformCallbacksAnyway */);
9502 else
9503 saveSettings();
9504 }
9505 }
9506
9507 if (!errorInSubtask)
9508 {
9509 /* set the result (this will try to fetch current error info on failure) */
9510 aTask.progress->notifyComplete (rc);
9511 }
9512
9513 if (SUCCEEDED (rc))
9514 mParent->onSnapshotDiscarded (mData->mUuid, Guid());
9515
9516 LogFlowThisFunc (("Done discarding current state (rc=%08X)\n", rc));
9517
9518 LogFlowThisFuncLeave();
9519}
9520
9521/**
9522 * Helper to change the machine state (reimplementation).
9523 *
9524 * @note Locks this object for writing.
9525 */
9526HRESULT SessionMachine::setMachineState (MachineState_T aMachineState)
9527{
9528 LogFlowThisFuncEnter();
9529 LogFlowThisFunc (("aMachineState=%d\n", aMachineState));
9530
9531 AutoCaller autoCaller (this);
9532 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
9533
9534 AutoLock alock (this);
9535
9536 MachineState_T oldMachineState = mData->mMachineState;
9537
9538 AssertMsgReturn (oldMachineState != aMachineState,
9539 ("oldMachineState=%d, aMachineState=%d\n",
9540 oldMachineState, aMachineState), E_FAIL);
9541
9542 HRESULT rc = S_OK;
9543
9544 int stsFlags = 0;
9545 bool deleteSavedState = false;
9546
9547 /* detect some state transitions */
9548
9549 if (oldMachineState < MachineState_Running &&
9550 aMachineState >= MachineState_Running &&
9551 aMachineState != MachineState_Discarding)
9552 {
9553 /*
9554 * the EMT thread is about to start, so mark attached HDDs as busy
9555 * and all its ancestors as being in use
9556 */
9557 for (HDData::HDAttachmentList::const_iterator it =
9558 mHDData->mHDAttachments.begin();
9559 it != mHDData->mHDAttachments.end();
9560 ++ it)
9561 {
9562 ComObjPtr <HardDisk> hd = (*it)->hardDisk();
9563 AutoLock hdLock (hd);
9564 hd->setBusy();
9565 hd->addReaderOnAncestors();
9566 }
9567 }
9568 else
9569 if (oldMachineState >= MachineState_Running &&
9570 oldMachineState != MachineState_Discarding &&
9571 aMachineState < MachineState_Running)
9572 {
9573 /*
9574 * the EMT thread stopped, so mark attached HDDs as no more busy
9575 * and remove the in-use flag from all its ancestors
9576 */
9577 for (HDData::HDAttachmentList::const_iterator it =
9578 mHDData->mHDAttachments.begin();
9579 it != mHDData->mHDAttachments.end();
9580 ++ it)
9581 {
9582 ComObjPtr <HardDisk> hd = (*it)->hardDisk();
9583 AutoLock hdLock (hd);
9584 hd->releaseReaderOnAncestors();
9585 hd->clearBusy();
9586 }
9587 }
9588
9589 if (oldMachineState == MachineState_Restoring)
9590 {
9591 if (aMachineState != MachineState_Saved)
9592 {
9593 /*
9594 * delete the saved state file once the machine has finished
9595 * restoring from it (note that Console sets the state from
9596 * Restoring to Saved if the VM couldn't restore successfully,
9597 * to give the user an ability to fix an error and retry --
9598 * we keep the saved state file in this case)
9599 */
9600 deleteSavedState = true;
9601 }
9602 }
9603 else
9604 if (oldMachineState == MachineState_Saved &&
9605 (aMachineState == MachineState_PoweredOff ||
9606 aMachineState == MachineState_Aborted))
9607 {
9608 /*
9609 * delete the saved state after Console::DiscardSavedState() is called
9610 * or if the VM process (owning a direct VM session) crashed while the
9611 * VM was Saved
9612 */
9613
9614 /// @todo (dmik)
9615 // Not sure that deleting the saved state file just because of the
9616 // client death before it attempted to restore the VM is a good
9617 // thing. But when it crashes we need to go to the Aborted state
9618 // which cannot have the saved state file associated... The only
9619 // way to fix this is to make the Aborted condition not a VM state
9620 // but a bool flag: i.e., when a crash occurs, set it to true and
9621 // change the state to PoweredOff or Saved depending on the
9622 // saved state presence.
9623
9624 deleteSavedState = true;
9625 mData->mCurrentStateModified = TRUE;
9626 stsFlags |= SaveSTS_CurStateModified;
9627 }
9628
9629 if (aMachineState == MachineState_Starting ||
9630 aMachineState == MachineState_Restoring)
9631 {
9632 /*
9633 * set the current state modified flag to indicate that the
9634 * current state is no more identical to the state in the
9635 * current snapshot
9636 */
9637 if (!mData->mCurrentSnapshot.isNull())
9638 {
9639 mData->mCurrentStateModified = TRUE;
9640 stsFlags |= SaveSTS_CurStateModified;
9641 }
9642 }
9643
9644 if (deleteSavedState == true)
9645 {
9646 Assert (!mSSData->mStateFilePath.isEmpty());
9647 RTFileDelete (Utf8Str (mSSData->mStateFilePath));
9648 mSSData->mStateFilePath.setNull();
9649 stsFlags |= SaveSTS_StateFilePath;
9650 }
9651
9652 /* redirect to the underlying peer machine */
9653 mPeer->setMachineState (aMachineState);
9654
9655 if (aMachineState == MachineState_PoweredOff ||
9656 aMachineState == MachineState_Aborted ||
9657 aMachineState == MachineState_Saved)
9658 {
9659 stsFlags |= SaveSTS_StateTimeStamp;
9660 }
9661
9662 rc = saveStateSettings (stsFlags);
9663
9664 if ((oldMachineState != MachineState_PoweredOff &&
9665 oldMachineState != MachineState_Aborted) &&
9666 (aMachineState == MachineState_PoweredOff ||
9667 aMachineState == MachineState_Aborted))
9668 {
9669 /*
9670 * clear differencing hard disks based on immutable hard disks
9671 * once we've been shut down for any reason
9672 */
9673 rc = wipeOutImmutableDiffs();
9674 }
9675
9676 LogFlowThisFunc (("rc=%08X\n", rc));
9677 LogFlowThisFuncLeave();
9678 return rc;
9679}
9680
9681/**
9682 * Sends the current machine state value to the VM process.
9683 *
9684 * @note Locks this object for reading, then calls a client process.
9685 */
9686HRESULT SessionMachine::updateMachineStateOnClient()
9687{
9688 AutoCaller autoCaller (this);
9689 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
9690
9691 ComPtr <IInternalSessionControl> directControl;
9692 {
9693 AutoReaderLock alock (this);
9694 AssertReturn (!!mData, E_FAIL);
9695 directControl = mData->mSession.mDirectControl;
9696
9697 /* directControl may be already set to NULL here in #OnSessionEnd()
9698 * called too early by the direct session process while there is still
9699 * some operation (like discarding the snapshot) in progress. The client
9700 * process in this case is waiting inside Session::close() for the
9701 * "end session" process object to complete, while #uninit() called by
9702 * #checkForDeath() on the Watcher thread is waiting for the pending
9703 * operation to complete. For now, we accept this inconsitent behavior
9704 * and simply do nothing here. */
9705
9706 if (mData->mSession.mState == SessionState_SessionClosing)
9707 return S_OK;
9708
9709 AssertReturn (!directControl.isNull(), E_FAIL);
9710 }
9711
9712 return directControl->UpdateMachineState (mData->mMachineState);
9713}
9714
9715/* static */
9716DECLCALLBACK(int) SessionMachine::taskHandler (RTTHREAD thread, void *pvUser)
9717{
9718 AssertReturn (pvUser, VERR_INVALID_POINTER);
9719
9720 Task *task = static_cast <Task *> (pvUser);
9721 task->handler();
9722
9723 // it's our responsibility to delete the task
9724 delete task;
9725
9726 return 0;
9727}
9728
9729/////////////////////////////////////////////////////////////////////////////
9730// SnapshotMachine class
9731/////////////////////////////////////////////////////////////////////////////
9732
9733DEFINE_EMPTY_CTOR_DTOR (SnapshotMachine)
9734
9735HRESULT SnapshotMachine::FinalConstruct()
9736{
9737 LogFlowThisFunc (("\n"));
9738
9739 /* set the proper type to indicate we're the SnapshotMachine instance */
9740 unconst (mType) = IsSnapshotMachine;
9741
9742 return S_OK;
9743}
9744
9745void SnapshotMachine::FinalRelease()
9746{
9747 LogFlowThisFunc (("\n"));
9748
9749 uninit();
9750}
9751
9752/**
9753 * Initializes the SnapshotMachine object when taking a snapshot.
9754 *
9755 * @param aSessionMachine machine to take a snapshot from
9756 * @param aSnapshotId snapshot ID of this snapshot machine
9757 * @param aStateFilePath file where the execution state will be later saved
9758 * (or NULL for the offline snapshot)
9759 *
9760 * @note Locks aSessionMachine object for reading.
9761 */
9762HRESULT SnapshotMachine::init (SessionMachine *aSessionMachine,
9763 INPTR GUIDPARAM aSnapshotId,
9764 INPTR BSTR aStateFilePath)
9765{
9766 LogFlowThisFuncEnter();
9767 LogFlowThisFunc (("mName={%ls}\n", aSessionMachine->mUserData->mName.raw()));
9768
9769 AssertReturn (aSessionMachine && !Guid (aSnapshotId).isEmpty(), E_INVALIDARG);
9770
9771 /* Enclose the state transition NotReady->InInit->Ready */
9772 AutoInitSpan autoInitSpan (this);
9773 AssertReturn (autoInitSpan.isOk(), E_UNEXPECTED);
9774
9775 mSnapshotId = aSnapshotId;
9776
9777 AutoReaderLock alock (aSessionMachine);
9778
9779 /* memorize the primary Machine instance (i.e. not SessionMachine!) */
9780 unconst (mPeer) = aSessionMachine->mPeer;
9781 /* share the parent pointer */
9782 unconst (mParent) = mPeer->mParent;
9783
9784 /* take the pointer to Data to share */
9785 mData.share (mPeer->mData);
9786 /*
9787 * take the pointer to UserData to share
9788 * (our UserData must always be the same as Machine's data)
9789 */
9790 mUserData.share (mPeer->mUserData);
9791 /* make a private copy of all other data (recent changes from SessionMachine) */
9792 mHWData.attachCopy (aSessionMachine->mHWData);
9793 mHDData.attachCopy (aSessionMachine->mHDData);
9794
9795 /* SSData is always unique for SnapshotMachine */
9796 mSSData.allocate();
9797 mSSData->mStateFilePath = aStateFilePath;
9798
9799 /*
9800 * create copies of all shared folders (mHWData after attiching a copy
9801 * contains just references to original objects)
9802 */
9803 for (HWData::SharedFolderList::iterator it = mHWData->mSharedFolders.begin();
9804 it != mHWData->mSharedFolders.end();
9805 ++ it)
9806 {
9807 ComObjPtr <SharedFolder> folder;
9808 folder.createObject();
9809 HRESULT rc = folder->initCopy (this, *it);
9810 CheckComRCReturnRC (rc);
9811 *it = folder;
9812 }
9813
9814 /* create all other child objects that will be immutable private copies */
9815
9816 unconst (mBIOSSettings).createObject();
9817 mBIOSSettings->initCopy (this, mPeer->mBIOSSettings);
9818
9819#ifdef VBOX_VRDP
9820 unconst (mVRDPServer).createObject();
9821 mVRDPServer->initCopy (this, mPeer->mVRDPServer);
9822#endif
9823
9824 unconst (mDVDDrive).createObject();
9825 mDVDDrive->initCopy (this, mPeer->mDVDDrive);
9826
9827 unconst (mFloppyDrive).createObject();
9828 mFloppyDrive->initCopy (this, mPeer->mFloppyDrive);
9829
9830 unconst (mAudioAdapter).createObject();
9831 mAudioAdapter->initCopy (this, mPeer->mAudioAdapter);
9832
9833 unconst (mUSBController).createObject();
9834 mUSBController->initCopy (this, mPeer->mUSBController);
9835
9836 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
9837 {
9838 unconst (mNetworkAdapters [slot]).createObject();
9839 mNetworkAdapters [slot]->initCopy (this, mPeer->mNetworkAdapters [slot]);
9840 }
9841
9842 /* Confirm a successful initialization when it's the case */
9843 autoInitSpan.setSucceeded();
9844
9845 LogFlowThisFuncLeave();
9846 return S_OK;
9847}
9848
9849/**
9850 * Initializes the SnapshotMachine object when loading from the settings file.
9851 *
9852 * @param aMachine machine the snapshot belngs to
9853 * @param aHWNode <Hardware> node
9854 * @param aHDAsNode <HardDiskAttachments> node
9855 * @param aSnapshotId snapshot ID of this snapshot machine
9856 * @param aStateFilePath file where the execution state is saved
9857 * (or NULL for the offline snapshot)
9858 *
9859 * @note Locks aMachine object for reading.
9860 */
9861HRESULT SnapshotMachine::init (Machine *aMachine, CFGNODE aHWNode, CFGNODE aHDAsNode,
9862 INPTR GUIDPARAM aSnapshotId, INPTR BSTR aStateFilePath)
9863{
9864 LogFlowThisFuncEnter();
9865 LogFlowThisFunc (("mName={%ls}\n", aMachine->mUserData->mName.raw()));
9866
9867 AssertReturn (aMachine && aHWNode && aHDAsNode && !Guid (aSnapshotId).isEmpty(),
9868 E_INVALIDARG);
9869
9870 /* Enclose the state transition NotReady->InInit->Ready */
9871 AutoInitSpan autoInitSpan (this);
9872 AssertReturn (autoInitSpan.isOk(), E_UNEXPECTED);
9873
9874 mSnapshotId = aSnapshotId;
9875
9876 AutoReaderLock alock (aMachine);
9877
9878 /* memorize the primary Machine instance */
9879 unconst (mPeer) = aMachine;
9880 /* share the parent pointer */
9881 unconst (mParent) = mPeer->mParent;
9882
9883 /* take the pointer to Data to share */
9884 mData.share (mPeer->mData);
9885 /*
9886 * take the pointer to UserData to share
9887 * (our UserData must always be the same as Machine's data)
9888 */
9889 mUserData.share (mPeer->mUserData);
9890 /* allocate private copies of all other data (will be loaded from settings) */
9891 mHWData.allocate();
9892 mHDData.allocate();
9893
9894 /* SSData is always unique for SnapshotMachine */
9895 mSSData.allocate();
9896 mSSData->mStateFilePath = aStateFilePath;
9897
9898 /* create all other child objects that will be immutable private copies */
9899
9900 unconst (mBIOSSettings).createObject();
9901 mBIOSSettings->init (this);
9902
9903#ifdef VBOX_VRDP
9904 unconst (mVRDPServer).createObject();
9905 mVRDPServer->init (this);
9906#endif
9907
9908 unconst (mDVDDrive).createObject();
9909 mDVDDrive->init (this);
9910
9911 unconst (mFloppyDrive).createObject();
9912 mFloppyDrive->init (this);
9913
9914 unconst (mAudioAdapter).createObject();
9915 mAudioAdapter->init (this);
9916
9917 unconst (mUSBController).createObject();
9918 mUSBController->init (this);
9919
9920 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
9921 {
9922 unconst (mNetworkAdapters [slot]).createObject();
9923 mNetworkAdapters [slot]->init (this, slot);
9924 }
9925
9926 /* load hardware and harddisk settings */
9927
9928 HRESULT rc = loadHardware (aHWNode);
9929 if (SUCCEEDED (rc))
9930 rc = loadHardDisks (aHDAsNode, true /* aRegistered */, &mSnapshotId);
9931
9932 if (SUCCEEDED (rc))
9933 {
9934 /* commit all changes made during the initialization */
9935 commit();
9936 }
9937
9938 /* Confirm a successful initialization when it's the case */
9939 if (SUCCEEDED (rc))
9940 autoInitSpan.setSucceeded();
9941
9942 LogFlowThisFuncLeave();
9943 return rc;
9944}
9945
9946/**
9947 * Uninitializes this SnapshotMachine object.
9948 */
9949void SnapshotMachine::uninit()
9950{
9951 LogFlowThisFuncEnter();
9952
9953 /* Enclose the state transition Ready->InUninit->NotReady */
9954 AutoUninitSpan autoUninitSpan (this);
9955 if (autoUninitSpan.uninitDone())
9956 return;
9957
9958 uninitDataAndChildObjects();
9959
9960 unconst (mParent).setNull();
9961 unconst (mPeer).setNull();
9962
9963 LogFlowThisFuncLeave();
9964}
9965
9966// AutoLock::Lockable interface
9967////////////////////////////////////////////////////////////////////////////////
9968
9969/**
9970 * Overrides VirtualBoxBase::lockHandle() in order to share the lock handle
9971 * with the primary Machine instance (mPeer).
9972 */
9973AutoLock::Handle *SnapshotMachine::lockHandle() const
9974{
9975 AssertReturn (!mPeer.isNull(), NULL);
9976 return mPeer->lockHandle();
9977}
9978
9979// public methods only for internal purposes
9980////////////////////////////////////////////////////////////////////////////////
9981
9982/**
9983 * Called by the snapshot object associated with this SnapshotMachine when
9984 * snapshot data such as name or description is changed.
9985 *
9986 * @note Locks this object for writing.
9987 */
9988HRESULT SnapshotMachine::onSnapshotChange (Snapshot *aSnapshot)
9989{
9990 AutoLock alock (this);
9991
9992 mPeer->saveSnapshotSettings (aSnapshot, SaveSS_UpdateAttrsOp);
9993
9994 /* inform callbacks */
9995 mParent->onSnapshotChange (mData->mUuid, aSnapshot->data().mId);
9996
9997 return S_OK;
9998}
Note: See TracBrowser for help on using the repository browser.

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