VirtualBox

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

Last change on this file since 8457 was 8388, checked in by vboxsync, 17 years ago

Main: Fixed very old regression: Failure to lock the machine settings file was not properly reported which led to assertions in debug build or to misbehavior in release builds. Examples of such failure were renaming the settings file manually or moving the machine directory w/o unregistering.

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