VirtualBox

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

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

Main/Settigs: Disabled File (..., RTFILE aHandle, ...) constructor since it is not thread-safe when the handle is shared across different File instances (needs a handle duplicate operation in the IPRT File API).

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

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