VirtualBox

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

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

Main: Pass inaccessible shared folders to the guest instead of silently ignoring them (#2425).

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

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