VirtualBox

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

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

Main: (guest properties) fix a burn and add an explanatory comment

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