VirtualBox

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

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

Main: Don't write a zero char to the end of a virgin global or machine XML settings file when creating it.

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