VirtualBox

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

Last change on this file since 7829 was 7611, checked in by vboxsync, 17 years ago

Main/FE/Qt: Use "GUI/Qt3" or "gui" session type to start Qt3 GUI and "GUI/Qt4" to start Qt4 GUI (supercedes r29094 and r29096).

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

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