VirtualBox

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

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

Main: use the HGCM guest property change notification interface and do some clean ups

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