VirtualBox

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

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

Main: Improved r41336.

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