VirtualBox

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

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

Correction

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

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette