VirtualBox

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

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

Main: clean up the guest property code - send accesses to the console from IMachine through IInternalSessionControl and do locking correctly

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