VirtualBox

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

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

Enabled VPID (VT-x tagged TLB); default off

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