VirtualBox

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

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

PerfAPI: Improved Win collector. More realistic performance test. No factories.

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