VirtualBox

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

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

Main: Lock the object before switching on non-const data members.

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

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