VirtualBox

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

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

Main (Guest Properties): another temporary testbox hack

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