VirtualBox

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

Last change on this file since 9937 was 9937, checked in by vboxsync, 16 years ago

Main: Corrected getProcessorUsage() impl (docs, spacing etc).

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