VirtualBox

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

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

Main: Renamed AutoLock => AutoWriteLock; AutoReaderLock => AutoReadLock.

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