VirtualBox

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

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

It's RTuuid not Ruuid.

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

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