VirtualBox

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

Last change on this file since 8579 was 8579, checked in by vboxsync, 17 years ago

AssertBreakVoid -> AssertBreak

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