VirtualBox

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

Last change on this file since 7449 was 7442, checked in by vboxsync, 17 years ago

Main: Applied SATA changes from #2406. Increased XML settings version format from 1.2 to 1.3.pre.

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

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