VirtualBox

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

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

#3551: “Main: Replace remaining collections with safe arrays”
Converted SharedFolderCollection. Approved by dmik. Tested with GUI.

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

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