VirtualBox

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

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

Darwin USB fixes.

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