VirtualBox

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

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

Moved the filter running over to USBProxyService (from Host). Split up the USBProxyService construction using an init() method like the rest of the classes.

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