VirtualBox

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

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

Main: Implemented detection of the unexpected spawning session termination for Windows (#3128), optimized for other platforms.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 353.2 KB
Line 
1/* $Id: MachineImpl.cpp 13457 2008-10-21 17:46:36Z 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 AssertRelease(!mHWData->mGuestPropertyNotificationPatterns.isNull());
6682 guestPropertiesNode.setValueOr <Bstr> ("notificationPatterns",
6683 mHWData->mGuestPropertyNotificationPatterns,
6684 Bstr (""));
6685 }
6686#endif /* VBOX_WITH_GUEST_PROPS defined */
6687
6688 AssertComRC (rc);
6689 return rc;
6690}
6691
6692/**
6693 * Saves the hard disk confguration.
6694 * It is assumed that the given node is empty.
6695 *
6696 * @param aNode <HardDiskAttachments> node to save the hard disk confguration to.
6697 */
6698HRESULT Machine::saveHardDisks (settings::Key &aNode)
6699{
6700 using namespace settings;
6701
6702 AssertReturn (!aNode.isNull(), E_INVALIDARG);
6703
6704 for (HDData::HDAttachmentList::const_iterator it = mHDData->mHDAttachments.begin();
6705 it != mHDData->mHDAttachments.end();
6706 ++ it)
6707 {
6708 ComObjPtr <HardDiskAttachment> att = *it;
6709
6710 Key hdNode = aNode.appendKey ("HardDiskAttachment");
6711
6712 {
6713 const char *bus = NULL;
6714 switch (att->bus())
6715 {
6716 case StorageBus_IDE: bus = "IDE"; break;
6717 case StorageBus_SATA: bus = "SATA"; break;
6718 default:
6719 ComAssertFailedRet (E_FAIL);
6720 }
6721
6722 hdNode.setValue <Guid> ("hardDisk", att->hardDisk()->id());
6723 hdNode.setStringValue ("bus", bus);
6724 hdNode.setValue <LONG> ("channel", att->channel());
6725 hdNode.setValue <LONG> ("device", att->device());
6726 }
6727 }
6728
6729 return S_OK;
6730}
6731
6732/**
6733 * Saves machine state settings as defined by aFlags
6734 * (SaveSTS_* values).
6735 *
6736 * @param aFlags Combination of SaveSTS_* flags.
6737 *
6738 * @note Locks objects for writing.
6739 */
6740HRESULT Machine::saveStateSettings (int aFlags)
6741{
6742 if (aFlags == 0)
6743 return S_OK;
6744
6745 AutoCaller autoCaller (this);
6746 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
6747
6748 /* This object's write lock is also necessary to serialize file access
6749 * (prevent concurrent reads and writes) */
6750 AutoWriteLock alock (this);
6751
6752 AssertReturn (isConfigLocked(), E_FAIL);
6753
6754 HRESULT rc = S_OK;
6755
6756 try
6757 {
6758 using namespace settings;
6759
6760 /* load the settings file */
6761 File file (mData->mHandleCfgFile, Utf8Str (mData->mConfigFileFull));
6762 XmlTreeBackend tree;
6763
6764 rc = VirtualBox::loadSettingsTree_ForUpdate (tree, file);
6765 CheckComRCReturnRC (rc);
6766
6767 Key machineNode = tree.rootKey().key ("Machine");
6768
6769 if (aFlags & SaveSTS_CurStateModified)
6770 {
6771 /* defaults to true */
6772 machineNode.setValueOr <bool> ("currentStateModified",
6773 !!mData->mCurrentStateModified, true);
6774 }
6775
6776 if (aFlags & SaveSTS_StateFilePath)
6777 {
6778 if (mSSData->mStateFilePath)
6779 {
6780 /* try to make the file name relative to the settings file dir */
6781 Utf8Str stateFilePath = mSSData->mStateFilePath;
6782 calculateRelativePath (stateFilePath, stateFilePath);
6783 machineNode.setStringValue ("stateFile", stateFilePath);
6784 }
6785 else
6786 machineNode.zapValue ("stateFile");
6787 }
6788
6789 if (aFlags & SaveSTS_StateTimeStamp)
6790 {
6791 Assert (mData->mMachineState != MachineState_Aborted ||
6792 mSSData->mStateFilePath.isNull());
6793
6794 machineNode.setValue <RTTIMESPEC> ("lastStateChange",
6795 mData->mLastStateChange);
6796
6797 /* set the aborted attribute when appropriate, defaults to false */
6798 machineNode.setValueOr <bool> ("aborted",
6799 mData->mMachineState == MachineState_Aborted,
6800 false);
6801 }
6802
6803 /* save settings on success */
6804 rc = VirtualBox::saveSettingsTree (tree, file,
6805 mData->mSettingsFileVersion);
6806 CheckComRCReturnRC (rc);
6807 }
6808 catch (...)
6809 {
6810 rc = VirtualBox::handleUnexpectedExceptions (RT_SRC_POS);
6811 }
6812
6813 return rc;
6814}
6815
6816/**
6817 * Cleans up all differencing hard disks based on immutable hard disks.
6818 *
6819 * @note Locks objects!
6820 */
6821HRESULT Machine::wipeOutImmutableDiffs()
6822{
6823 AutoCaller autoCaller (this);
6824 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
6825
6826 AutoReadLock alock (this);
6827
6828 AssertReturn (mData->mMachineState == MachineState_PoweredOff ||
6829 mData->mMachineState == MachineState_Aborted, E_FAIL);
6830
6831 for (HDData::HDAttachmentList::const_iterator it = mHDData->mHDAttachments.begin();
6832 it != mHDData->mHDAttachments.end();
6833 ++ it)
6834 {
6835 ComObjPtr <HardDisk> hd = (*it)->hardDisk();
6836 AutoWriteLock hdLock (hd);
6837
6838 if(hd->isParentImmutable())
6839 {
6840 /// @todo (dmik) no error handling for now
6841 // (need async error reporting for this)
6842 hd->asVDI()->wipeOutImage();
6843 }
6844 }
6845
6846 return S_OK;
6847}
6848
6849/**
6850 * Fixes up lazy hard disk attachments by creating or deleting differencing
6851 * hard disks when machine settings are being committed.
6852 * Must be called only from #commit().
6853 *
6854 * @note Locks objects!
6855 */
6856HRESULT Machine::fixupHardDisks (bool aCommit)
6857{
6858 AutoCaller autoCaller (this);
6859 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
6860
6861 AutoWriteLock alock (this);
6862
6863 /* no attac/detach operations -- nothing to do */
6864 if (!mHDData.isBackedUp())
6865 {
6866 mHDData->mHDAttachmentsChanged = false;
6867 return S_OK;
6868 }
6869
6870 AssertReturn (mData->mRegistered, E_FAIL);
6871
6872 if (aCommit)
6873 {
6874 /*
6875 * changes are being committed,
6876 * perform actual diff image creation, deletion etc.
6877 */
6878
6879 /* take a copy of backed up attachments (will modify it) */
6880 HDData::HDAttachmentList backedUp = mHDData.backedUpData()->mHDAttachments;
6881 /* list of new diffs created */
6882 std::list <ComObjPtr <HardDisk> > newDiffs;
6883
6884 HRESULT rc = S_OK;
6885
6886 /* go through current attachments */
6887 for (HDData::HDAttachmentList::const_iterator
6888 it = mHDData->mHDAttachments.begin();
6889 it != mHDData->mHDAttachments.end();
6890 ++ it)
6891 {
6892 ComObjPtr <HardDiskAttachment> hda = *it;
6893 ComObjPtr <HardDisk> hd = hda->hardDisk();
6894 AutoWriteLock hdLock (hd);
6895
6896 if (!hda->isDirty())
6897 {
6898 /*
6899 * not dirty, therefore was either attached before backing up
6900 * or doesn't need any fixup (already fixed up); try to locate
6901 * this hard disk among backed up attachments and remove from
6902 * there to prevent it from being deassociated/deleted
6903 */
6904 HDData::HDAttachmentList::iterator oldIt;
6905 for (oldIt = backedUp.begin(); oldIt != backedUp.end(); ++ oldIt)
6906 if ((*oldIt)->hardDisk().equalsTo (hd))
6907 break;
6908 if (oldIt != backedUp.end())
6909 {
6910 /* remove from there */
6911 backedUp.erase (oldIt);
6912 Log3 (("FC: %ls found in old\n", hd->toString().raw()));
6913 }
6914 }
6915 else
6916 {
6917 /* dirty, determine what to do */
6918
6919 bool needDiff = false;
6920 bool searchAmongSnapshots = false;
6921
6922 switch (hd->type())
6923 {
6924 case HardDiskType_Immutable:
6925 {
6926 /* decrease readers increased in AttachHardDisk() */
6927 hd->releaseReader();
6928 Log3 (("FC: %ls released\n", hd->toString().raw()));
6929 /* indicate we need a diff (indirect attachment) */
6930 needDiff = true;
6931 break;
6932 }
6933 case HardDiskType_Writethrough:
6934 {
6935 /* reset the dirty flag */
6936 hda->updateHardDisk (hd, false /* aDirty */);
6937 Log3 (("FC: %ls updated\n", hd->toString().raw()));
6938 break;
6939 }
6940 case HardDiskType_Normal:
6941 {
6942 if (hd->snapshotId().isEmpty())
6943 {
6944 /* reset the dirty flag */
6945 hda->updateHardDisk (hd, false /* aDirty */);
6946 Log3 (("FC: %ls updated\n", hd->toString().raw()));
6947 }
6948 else
6949 {
6950 /* decrease readers increased in AttachHardDisk() */
6951 hd->releaseReader();
6952 Log3 (("FC: %ls released\n", hd->toString().raw()));
6953 /* indicate we need a diff (indirect attachment) */
6954 needDiff = true;
6955 /* search for the most recent base among snapshots */
6956 searchAmongSnapshots = true;
6957 }
6958 break;
6959 }
6960 }
6961
6962 if (!needDiff)
6963 continue;
6964
6965 bool createDiff = false;
6966
6967 /*
6968 * see whether any previously attached hard disk has the
6969 * the currently attached one (Normal or Independent) as
6970 * the root
6971 */
6972
6973 HDData::HDAttachmentList::iterator foundIt = backedUp.end();
6974
6975 for (HDData::HDAttachmentList::iterator it = backedUp.begin();
6976 it != backedUp.end();
6977 ++ it)
6978 {
6979 if ((*it)->hardDisk()->root().equalsTo (hd))
6980 {
6981 /*
6982 * matched dev and ctl (i.e. attached to the same place)
6983 * will win and immediately stop the search; otherwise
6984 * the first attachment that matched the hd only will
6985 * be used
6986 */
6987 if ((*it)->device() == hda->device() &&
6988 (*it)->channel() == hda->channel() &&
6989 (*it)->bus() == hda->bus())
6990 {
6991 foundIt = it;
6992 break;
6993 }
6994 else
6995 if (foundIt == backedUp.end())
6996 {
6997 /*
6998 * not an exact match; ensure there is no exact match
6999 * among other current attachments referring the same
7000 * root (to prevent this attachmend from reusing the
7001 * hard disk of the other attachment that will later
7002 * give the exact match or already gave it before)
7003 */
7004 bool canReuse = true;
7005 for (HDData::HDAttachmentList::const_iterator
7006 it2 = mHDData->mHDAttachments.begin();
7007 it2 != mHDData->mHDAttachments.end();
7008 ++ it2)
7009 {
7010 if ((*it2)->device() == (*it)->device() &&
7011 (*it2)->channel() == (*it)->channel() &&
7012 (*it2)->bus() == (*it)->bus() &&
7013 (*it2)->hardDisk()->root().equalsTo (hd))
7014 {
7015 /*
7016 * the exact match, either non-dirty or dirty
7017 * one refers the same root: in both cases
7018 * we cannot reuse the hard disk, so break
7019 */
7020 canReuse = false;
7021 break;
7022 }
7023 }
7024
7025 if (canReuse)
7026 foundIt = it;
7027 }
7028 }
7029 }
7030
7031 if (foundIt != backedUp.end())
7032 {
7033 /* found either one or another, reuse the diff */
7034 hda->updateHardDisk ((*foundIt)->hardDisk(),
7035 false /* aDirty */);
7036 Log3 (("FC: %ls reused as %ls\n", hd->toString().raw(),
7037 (*foundIt)->hardDisk()->toString().raw()));
7038 /* remove from there */
7039 backedUp.erase (foundIt);
7040 }
7041 else
7042 {
7043 /* was not attached, need a diff */
7044 createDiff = true;
7045 }
7046
7047 if (!createDiff)
7048 continue;
7049
7050 ComObjPtr <HardDisk> baseHd = hd;
7051
7052 if (searchAmongSnapshots)
7053 {
7054 /*
7055 * find the most recent diff based on the currently
7056 * attached root (Normal hard disk) among snapshots
7057 */
7058
7059 ComObjPtr <Snapshot> snap = mData->mCurrentSnapshot;
7060
7061 while (snap)
7062 {
7063 AutoWriteLock snapLock (snap);
7064
7065 const HDData::HDAttachmentList &snapAtts =
7066 snap->data().mMachine->mHDData->mHDAttachments;
7067
7068 HDData::HDAttachmentList::const_iterator foundIt = snapAtts.end();
7069
7070 for (HDData::HDAttachmentList::const_iterator
7071 it = snapAtts.begin(); it != snapAtts.end(); ++ it)
7072 {
7073 if ((*it)->hardDisk()->root().equalsTo (hd))
7074 {
7075 /*
7076 * matched dev and ctl (i.e. attached to the same place)
7077 * will win and immediately stop the search; otherwise
7078 * the first attachment that matched the hd only will
7079 * be used
7080 */
7081 if ((*it)->device() == hda->device() &&
7082 (*it)->channel() == hda->channel() &&
7083 (*it)->bus() == hda->bus())
7084 {
7085 foundIt = it;
7086 break;
7087 }
7088 else
7089 if (foundIt == snapAtts.end())
7090 foundIt = it;
7091 }
7092 }
7093
7094 if (foundIt != snapAtts.end())
7095 {
7096 /* the most recent diff has been found, use as a base */
7097 baseHd = (*foundIt)->hardDisk();
7098 Log3 (("FC: %ls: recent found %ls\n",
7099 hd->toString().raw(), baseHd->toString().raw()));
7100 break;
7101 }
7102
7103 snap = snap->parent();
7104 }
7105 }
7106
7107 /* create a new diff for the hard disk being indirectly attached */
7108
7109 AutoWriteLock baseHdLock (baseHd);
7110 baseHd->addReader();
7111
7112 ComObjPtr <HVirtualDiskImage> vdi;
7113 rc = baseHd->createDiffHardDisk (mUserData->mSnapshotFolderFull,
7114 mData->mUuid, vdi, NULL);
7115 baseHd->releaseReader();
7116 CheckComRCBreakRC (rc);
7117
7118 newDiffs.push_back (ComObjPtr <HardDisk> (vdi));
7119
7120 /* update the attachment and reset the dirty flag */
7121 hda->updateHardDisk (ComObjPtr <HardDisk> (vdi),
7122 false /* aDirty */);
7123 Log3 (("FC: %ls: diff created %ls\n",
7124 baseHd->toString().raw(), vdi->toString().raw()));
7125 }
7126 }
7127
7128 if (FAILED (rc))
7129 {
7130 /* delete diffs we created */
7131 for (std::list <ComObjPtr <HardDisk> >::const_iterator
7132 it = newDiffs.begin(); it != newDiffs.end(); ++ it)
7133 {
7134 /*
7135 * unregisterDiffHardDisk() is supposed to delete and uninit
7136 * the differencing hard disk
7137 */
7138 mParent->unregisterDiffHardDisk (*it);
7139 /* too bad if we fail here, but nothing to do, just continue */
7140 }
7141
7142 /* the best is to rollback the changes... */
7143 mHDData.rollback();
7144 mHDData->mHDAttachmentsChanged = false;
7145 Log3 (("FC: ROLLED BACK\n"));
7146 return rc;
7147 }
7148
7149 /*
7150 * go through the rest of old attachments and delete diffs
7151 * or deassociate hard disks from machines (they will become detached)
7152 */
7153 for (HDData::HDAttachmentList::iterator
7154 it = backedUp.begin(); it != backedUp.end(); ++ it)
7155 {
7156 ComObjPtr <HardDiskAttachment> hda = *it;
7157 ComObjPtr <HardDisk> hd = hda->hardDisk();
7158 AutoWriteLock hdLock (hd);
7159
7160 if (hd->isDifferencing())
7161 {
7162 /*
7163 * unregisterDiffHardDisk() is supposed to delete and uninit
7164 * the differencing hard disk
7165 */
7166 Log3 (("FC: %ls diff deleted\n", hd->toString().raw()));
7167 rc = mParent->unregisterDiffHardDisk (hd);
7168 /*
7169 * too bad if we fail here, but nothing to do, just continue
7170 * (the last rc will be returned to the caller though)
7171 */
7172 }
7173 else
7174 {
7175 /* deassociate from this machine */
7176 Log3 (("FC: %ls deassociated\n", hd->toString().raw()));
7177 hd->setMachineId (Guid());
7178 }
7179 }
7180
7181 /* commit all the changes */
7182 mHDData->mHDAttachmentsChanged = mHDData.hasActualChanges();
7183 mHDData.commit();
7184 Log3 (("FC: COMMITTED\n"));
7185
7186 return rc;
7187 }
7188
7189 /*
7190 * changes are being rolled back,
7191 * go trhough all current attachments and fix up dirty ones
7192 * the way it is done in DetachHardDisk()
7193 */
7194
7195 for (HDData::HDAttachmentList::iterator it = mHDData->mHDAttachments.begin();
7196 it != mHDData->mHDAttachments.end();
7197 ++ it)
7198 {
7199 ComObjPtr <HardDiskAttachment> hda = *it;
7200 ComObjPtr <HardDisk> hd = hda->hardDisk();
7201 AutoWriteLock hdLock (hd);
7202
7203 if (hda->isDirty())
7204 {
7205 switch (hd->type())
7206 {
7207 case HardDiskType_Immutable:
7208 {
7209 /* decrease readers increased in AttachHardDisk() */
7210 hd->releaseReader();
7211 Log3 (("FR: %ls released\n", hd->toString().raw()));
7212 break;
7213 }
7214 case HardDiskType_Writethrough:
7215 {
7216 /* deassociate from this machine */
7217 hd->setMachineId (Guid());
7218 Log3 (("FR: %ls deassociated\n", hd->toString().raw()));
7219 break;
7220 }
7221 case HardDiskType_Normal:
7222 {
7223 if (hd->snapshotId().isEmpty())
7224 {
7225 /* deassociate from this machine */
7226 hd->setMachineId (Guid());
7227 Log3 (("FR: %ls deassociated\n", hd->toString().raw()));
7228 }
7229 else
7230 {
7231 /* decrease readers increased in AttachHardDisk() */
7232 hd->releaseReader();
7233 Log3 (("FR: %ls released\n", hd->toString().raw()));
7234 }
7235
7236 break;
7237 }
7238 }
7239 }
7240 }
7241
7242 /* rollback all the changes */
7243 mHDData.rollback();
7244 Log3 (("FR: ROLLED BACK\n"));
7245
7246 return S_OK;
7247}
7248
7249/**
7250 * Creates differencing hard disks for all normal hard disks
7251 * and replaces attachments to refer to created disks.
7252 * Used when taking a snapshot or when discarding the current state.
7253 *
7254 * @param aSnapshotId ID of the snapshot being taken
7255 * or NULL if the current state is being discarded
7256 * @param aFolder folder where to create diff. hard disks
7257 * @param aProgress progress object to run (must contain at least as
7258 * many operations left as the number of VDIs attached)
7259 * @param aOnline whether the machine is online (i.e., when the EMT
7260 * thread is paused, OR when current hard disks are
7261 * marked as busy for some other reason)
7262 *
7263 * @note
7264 * The progress object is not marked as completed, neither on success
7265 * nor on failure. This is a responsibility of the caller.
7266 *
7267 * @note Locks mParent + this object for writing
7268 */
7269HRESULT Machine::createSnapshotDiffs (const Guid *aSnapshotId,
7270 const Bstr &aFolder,
7271 const ComObjPtr <Progress> &aProgress,
7272 bool aOnline)
7273{
7274 AssertReturn (!aFolder.isEmpty(), E_FAIL);
7275
7276 AutoCaller autoCaller (this);
7277 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7278
7279 /* accessing mParent methods below needs mParent lock */
7280 AutoMultiWriteLock2 alock (mParent, this);
7281
7282 HRESULT rc = S_OK;
7283
7284 // first pass: check accessibility before performing changes
7285 if (!aOnline)
7286 {
7287 for (HDData::HDAttachmentList::const_iterator it = mHDData->mHDAttachments.begin();
7288 it != mHDData->mHDAttachments.end();
7289 ++ it)
7290 {
7291 ComObjPtr <HardDiskAttachment> hda = *it;
7292 ComObjPtr <HardDisk> hd = hda->hardDisk();
7293 AutoWriteLock hdLock (hd);
7294
7295 ComAssertMsgBreak (hd->type() == HardDiskType_Normal,
7296 ("Invalid hard disk type %d\n", hd->type()),
7297 rc = E_FAIL);
7298
7299 ComAssertMsgBreak (!hd->isParentImmutable() ||
7300 hd->storageType() == HardDiskStorageType_VirtualDiskImage,
7301 ("Invalid hard disk storage type %d\n", hd->storageType()),
7302 rc = E_FAIL);
7303
7304 Bstr accessError;
7305 rc = hd->getAccessible (accessError);
7306 CheckComRCBreakRC (rc);
7307
7308 if (!accessError.isNull())
7309 {
7310 rc = setError (E_FAIL,
7311 tr ("Hard disk '%ls' is not accessible (%ls)"),
7312 hd->toString().raw(), accessError.raw());
7313 break;
7314 }
7315 }
7316 CheckComRCReturnRC (rc);
7317 }
7318
7319 HDData::HDAttachmentList attachments;
7320
7321 // second pass: perform changes
7322 for (HDData::HDAttachmentList::const_iterator it = mHDData->mHDAttachments.begin();
7323 it != mHDData->mHDAttachments.end();
7324 ++ it)
7325 {
7326 ComObjPtr <HardDiskAttachment> hda = *it;
7327 ComObjPtr <HardDisk> hd = hda->hardDisk();
7328 AutoWriteLock hdLock (hd);
7329
7330 ComObjPtr <HardDisk> parent = hd->parent();
7331 AutoWriteLock parentHdLock (parent);
7332
7333 ComObjPtr <HardDisk> newHd;
7334
7335 // clear busy flag if the VM is online
7336 if (aOnline)
7337 hd->clearBusy();
7338 // increase readers
7339 hd->addReader();
7340
7341 if (hd->isParentImmutable())
7342 {
7343 aProgress->advanceOperation (Bstr (Utf8StrFmt (
7344 tr ("Preserving immutable hard disk '%ls'"),
7345 parent->toString (true /* aShort */).raw())));
7346
7347 parentHdLock.unlock();
7348 alock.leave();
7349
7350 // create a copy of the independent diff
7351 ComObjPtr <HVirtualDiskImage> vdi;
7352 rc = hd->asVDI()->cloneDiffImage (aFolder, mData->mUuid, vdi,
7353 aProgress);
7354 newHd = vdi;
7355
7356 alock.enter();
7357 parentHdLock.lock();
7358
7359 // decrease readers (hd is no more used for reading in any case)
7360 hd->releaseReader();
7361 }
7362 else
7363 {
7364 // checked in the first pass
7365 Assert (hd->type() == HardDiskType_Normal);
7366
7367 aProgress->advanceOperation (Bstr (Utf8StrFmt (
7368 tr ("Creating a differencing hard disk for '%ls'"),
7369 hd->root()->toString (true /* aShort */).raw())));
7370
7371 parentHdLock.unlock();
7372 alock.leave();
7373
7374 // create a new diff for the image being attached
7375 ComObjPtr <HVirtualDiskImage> vdi;
7376 rc = hd->createDiffHardDisk (aFolder, mData->mUuid, vdi, aProgress);
7377 newHd = vdi;
7378
7379 alock.enter();
7380 parentHdLock.lock();
7381
7382 if (SUCCEEDED (rc))
7383 {
7384 // if online, hd must keep a reader referece
7385 if (!aOnline)
7386 hd->releaseReader();
7387 }
7388 else
7389 {
7390 // decrease readers
7391 hd->releaseReader();
7392 }
7393 }
7394
7395 if (SUCCEEDED (rc))
7396 {
7397 ComObjPtr <HardDiskAttachment> newHda;
7398 newHda.createObject();
7399 rc = newHda->init (newHd, hda->bus(), hda->channel(), hda->device(),
7400 false /* aDirty */);
7401
7402 if (SUCCEEDED (rc))
7403 {
7404 // associate the snapshot id with the old hard disk
7405 if (hd->type() != HardDiskType_Writethrough && aSnapshotId)
7406 hd->setSnapshotId (*aSnapshotId);
7407
7408 // add the new attachment
7409 attachments.push_back (newHda);
7410
7411 // if online, newHd must be marked as busy
7412 if (aOnline)
7413 newHd->setBusy();
7414 }
7415 }
7416
7417 if (FAILED (rc))
7418 {
7419 // set busy flag back if the VM is online
7420 if (aOnline)
7421 hd->setBusy();
7422 break;
7423 }
7424 }
7425
7426 if (SUCCEEDED (rc))
7427 {
7428 // replace the whole list of attachments with the new one
7429 mHDData->mHDAttachments = attachments;
7430 }
7431 else
7432 {
7433 // delete those diffs we've just created
7434 for (HDData::HDAttachmentList::const_iterator it = attachments.begin();
7435 it != attachments.end();
7436 ++ it)
7437 {
7438 ComObjPtr <HardDisk> hd = (*it)->hardDisk();
7439 AutoWriteLock hdLock (hd);
7440 Assert (hd->children().size() == 0);
7441 Assert (hd->isDifferencing());
7442 // unregisterDiffHardDisk() is supposed to delete and uninit
7443 // the differencing hard disk
7444 mParent->unregisterDiffHardDisk (hd);
7445 }
7446 }
7447
7448 return rc;
7449}
7450
7451/**
7452 * Deletes differencing hard disks created by createSnapshotDiffs() in case
7453 * if snapshot creation was failed.
7454 *
7455 * @param aSnapshot failed snapshot
7456 *
7457 * @note Locks mParent + this object for writing.
7458 */
7459HRESULT Machine::deleteSnapshotDiffs (const ComObjPtr <Snapshot> &aSnapshot)
7460{
7461 AssertReturn (!aSnapshot.isNull(), E_FAIL);
7462
7463 AutoCaller autoCaller (this);
7464 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7465
7466 /* accessing mParent methods below needs mParent lock */
7467 AutoMultiWriteLock2 alock (mParent, this);
7468
7469 /* short cut: check whether attachments are all the same */
7470 if (mHDData->mHDAttachments == aSnapshot->data().mMachine->mHDData->mHDAttachments)
7471 return S_OK;
7472
7473 HRESULT rc = S_OK;
7474
7475 for (HDData::HDAttachmentList::const_iterator it = mHDData->mHDAttachments.begin();
7476 it != mHDData->mHDAttachments.end();
7477 ++ it)
7478 {
7479 ComObjPtr <HardDiskAttachment> hda = *it;
7480 ComObjPtr <HardDisk> hd = hda->hardDisk();
7481 AutoWriteLock hdLock (hd);
7482
7483 ComObjPtr <HardDisk> parent = hd->parent();
7484 AutoWriteLock parentHdLock (parent);
7485
7486 if (!parent || parent->snapshotId() != aSnapshot->data().mId)
7487 continue;
7488
7489 /* must not have children */
7490 ComAssertRet (hd->children().size() == 0, E_FAIL);
7491
7492 /* deassociate the old hard disk from the given snapshot's ID */
7493 parent->setSnapshotId (Guid());
7494
7495 /* unregisterDiffHardDisk() is supposed to delete and uninit
7496 * the differencing hard disk */
7497 rc = mParent->unregisterDiffHardDisk (hd);
7498 /* continue on error */
7499 }
7500
7501 /* restore the whole list of attachments from the failed snapshot */
7502 mHDData->mHDAttachments = aSnapshot->data().mMachine->mHDData->mHDAttachments;
7503
7504 return rc;
7505}
7506
7507/**
7508 * Helper to lock the machine configuration for write access.
7509 *
7510 * @return S_OK or E_FAIL and sets error info on failure
7511 *
7512 * @note Doesn't lock anything (must be called from this object's lock)
7513 */
7514HRESULT Machine::lockConfig()
7515{
7516 HRESULT rc = S_OK;
7517
7518 if (!isConfigLocked())
7519 {
7520 /* open the associated config file */
7521 int vrc = RTFileOpen (&mData->mHandleCfgFile,
7522 Utf8Str (mData->mConfigFileFull),
7523 RTFILE_O_READWRITE | RTFILE_O_OPEN |
7524 RTFILE_O_DENY_WRITE);
7525 if (VBOX_FAILURE (vrc))
7526 {
7527 mData->mHandleCfgFile = NIL_RTFILE;
7528
7529 rc = setError (E_FAIL,
7530 tr ("Could not lock the settings file '%ls' (%Vrc)"),
7531 mData->mConfigFileFull.raw(), vrc);
7532 }
7533 }
7534
7535 LogFlowThisFunc (("mConfigFile={%ls}, mHandleCfgFile=%d, rc=%08X\n",
7536 mData->mConfigFileFull.raw(), mData->mHandleCfgFile, rc));
7537 return rc;
7538}
7539
7540/**
7541 * Helper to unlock the machine configuration from write access
7542 *
7543 * @return S_OK
7544 *
7545 * @note Doesn't lock anything.
7546 * @note Not thread safe (must be called from this object's lock).
7547 */
7548HRESULT Machine::unlockConfig()
7549{
7550 HRESULT rc = S_OK;
7551
7552 if (isConfigLocked())
7553 {
7554 RTFileFlush(mData->mHandleCfgFile);
7555 RTFileClose(mData->mHandleCfgFile);
7556 /** @todo flush the directory. */
7557 mData->mHandleCfgFile = NIL_RTFILE;
7558 }
7559
7560 LogFlowThisFunc (("\n"));
7561
7562 return rc;
7563}
7564
7565/**
7566 * Returns true if the settings file is located in the directory named exactly
7567 * as the machine. This will be true if the machine settings structure was
7568 * created by default in #openConfigLoader().
7569 *
7570 * @param aSettingsDir if not NULL, the full machine settings file directory
7571 * name will be assigned there.
7572 *
7573 * @note Doesn't lock anything.
7574 * @note Not thread safe (must be called from this object's lock).
7575 */
7576bool Machine::isInOwnDir (Utf8Str *aSettingsDir /* = NULL */)
7577{
7578 Utf8Str settingsDir = mData->mConfigFileFull;
7579 RTPathStripFilename (settingsDir.mutableRaw());
7580 char *dirName = RTPathFilename (settingsDir);
7581
7582 AssertReturn (dirName, false);
7583
7584 /* if we don't rename anything on name change, return false shorlty */
7585 if (!mUserData->mNameSync)
7586 return false;
7587
7588 if (aSettingsDir)
7589 *aSettingsDir = settingsDir;
7590
7591 return Bstr (dirName) == mUserData->mName;
7592}
7593
7594/**
7595 * @note Locks objects for reading!
7596 */
7597bool Machine::isModified()
7598{
7599 AutoCaller autoCaller (this);
7600 AssertComRCReturn (autoCaller.rc(), false);
7601
7602 AutoReadLock alock (this);
7603
7604 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
7605 if (mNetworkAdapters [slot] && mNetworkAdapters [slot]->isModified())
7606 return true;
7607
7608 for (ULONG slot = 0; slot < ELEMENTS (mSerialPorts); slot ++)
7609 if (mSerialPorts [slot] && mSerialPorts [slot]->isModified())
7610 return true;
7611
7612 for (ULONG slot = 0; slot < ELEMENTS (mParallelPorts); slot ++)
7613 if (mParallelPorts [slot] && mParallelPorts [slot]->isModified())
7614 return true;
7615
7616 return
7617 mUserData.isBackedUp() ||
7618 mHWData.isBackedUp() ||
7619 mHDData.isBackedUp() ||
7620#ifdef VBOX_WITH_VRDP
7621 (mVRDPServer && mVRDPServer->isModified()) ||
7622#endif
7623 (mDVDDrive && mDVDDrive->isModified()) ||
7624 (mFloppyDrive && mFloppyDrive->isModified()) ||
7625 (mAudioAdapter && mAudioAdapter->isModified()) ||
7626 (mUSBController && mUSBController->isModified()) ||
7627 (mSATAController && mSATAController->isModified()) ||
7628 (mBIOSSettings && mBIOSSettings->isModified());
7629}
7630
7631/**
7632 * @note This method doesn't check (ignores) actual changes to mHDData.
7633 * Use mHDData.mHDAttachmentsChanged right after #commit() instead.
7634 *
7635 * @param aIgnoreUserData |true| to ignore changes to mUserData
7636 *
7637 * @note Locks objects for reading!
7638 */
7639bool Machine::isReallyModified (bool aIgnoreUserData /* = false */)
7640{
7641 AutoCaller autoCaller (this);
7642 AssertComRCReturn (autoCaller.rc(), false);
7643
7644 AutoReadLock alock (this);
7645
7646 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
7647 if (mNetworkAdapters [slot] && mNetworkAdapters [slot]->isReallyModified())
7648 return true;
7649
7650 for (ULONG slot = 0; slot < ELEMENTS (mSerialPorts); slot ++)
7651 if (mSerialPorts [slot] && mSerialPorts [slot]->isReallyModified())
7652 return true;
7653
7654 for (ULONG slot = 0; slot < ELEMENTS (mParallelPorts); slot ++)
7655 if (mParallelPorts [slot] && mParallelPorts [slot]->isReallyModified())
7656 return true;
7657
7658 return
7659 (!aIgnoreUserData && mUserData.hasActualChanges()) ||
7660 mHWData.hasActualChanges() ||
7661 /* ignore mHDData */
7662 //mHDData.hasActualChanges() ||
7663#ifdef VBOX_WITH_VRDP
7664 (mVRDPServer && mVRDPServer->isReallyModified()) ||
7665#endif
7666 (mDVDDrive && mDVDDrive->isReallyModified()) ||
7667 (mFloppyDrive && mFloppyDrive->isReallyModified()) ||
7668 (mAudioAdapter && mAudioAdapter->isReallyModified()) ||
7669 (mUSBController && mUSBController->isReallyModified()) ||
7670 (mSATAController && mSATAController->isReallyModified()) ||
7671 (mBIOSSettings && mBIOSSettings->isReallyModified());
7672}
7673
7674/**
7675 * Discards all changes to machine settings.
7676 *
7677 * @param aNotify whether to notify the direct session about changes or not
7678 *
7679 * @note Locks objects!
7680 */
7681void Machine::rollback (bool aNotify)
7682{
7683 AutoCaller autoCaller (this);
7684 AssertComRCReturn (autoCaller.rc(), (void) 0);
7685
7686 AutoWriteLock alock (this);
7687
7688 /* check for changes in own data */
7689
7690 bool sharedFoldersChanged = false;
7691
7692 if (aNotify && mHWData.isBackedUp())
7693 {
7694 if (mHWData->mSharedFolders.size() !=
7695 mHWData.backedUpData()->mSharedFolders.size())
7696 sharedFoldersChanged = true;
7697 else
7698 {
7699 for (HWData::SharedFolderList::iterator rit =
7700 mHWData->mSharedFolders.begin();
7701 rit != mHWData->mSharedFolders.end() && !sharedFoldersChanged;
7702 ++ rit)
7703 {
7704 for (HWData::SharedFolderList::iterator cit =
7705 mHWData.backedUpData()->mSharedFolders.begin();
7706 cit != mHWData.backedUpData()->mSharedFolders.end();
7707 ++ cit)
7708 {
7709 if ((*cit)->name() != (*rit)->name() ||
7710 (*cit)->hostPath() != (*rit)->hostPath())
7711 {
7712 sharedFoldersChanged = true;
7713 break;
7714 }
7715 }
7716 }
7717 }
7718 }
7719
7720 mUserData.rollback();
7721
7722 mHWData.rollback();
7723
7724 if (mHDData.isBackedUp())
7725 fixupHardDisks (false /* aCommit */);
7726
7727 /* check for changes in child objects */
7728
7729 bool vrdpChanged = false, dvdChanged = false, floppyChanged = false,
7730 usbChanged = false, sataChanged = false;
7731
7732 ComPtr <INetworkAdapter> networkAdapters [ELEMENTS (mNetworkAdapters)];
7733 ComPtr <ISerialPort> serialPorts [ELEMENTS (mSerialPorts)];
7734 ComPtr <IParallelPort> parallelPorts [ELEMENTS (mParallelPorts)];
7735
7736 if (mBIOSSettings)
7737 mBIOSSettings->rollback();
7738
7739#ifdef VBOX_WITH_VRDP
7740 if (mVRDPServer)
7741 vrdpChanged = mVRDPServer->rollback();
7742#endif
7743
7744 if (mDVDDrive)
7745 dvdChanged = mDVDDrive->rollback();
7746
7747 if (mFloppyDrive)
7748 floppyChanged = mFloppyDrive->rollback();
7749
7750 if (mAudioAdapter)
7751 mAudioAdapter->rollback();
7752
7753 if (mUSBController)
7754 usbChanged = mUSBController->rollback();
7755
7756 if (mSATAController)
7757 sataChanged = mSATAController->rollback();
7758
7759 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
7760 if (mNetworkAdapters [slot])
7761 if (mNetworkAdapters [slot]->rollback())
7762 networkAdapters [slot] = mNetworkAdapters [slot];
7763
7764 for (ULONG slot = 0; slot < ELEMENTS (mSerialPorts); slot ++)
7765 if (mSerialPorts [slot])
7766 if (mSerialPorts [slot]->rollback())
7767 serialPorts [slot] = mSerialPorts [slot];
7768
7769 for (ULONG slot = 0; slot < ELEMENTS (mParallelPorts); slot ++)
7770 if (mParallelPorts [slot])
7771 if (mParallelPorts [slot]->rollback())
7772 parallelPorts [slot] = mParallelPorts [slot];
7773
7774 if (aNotify)
7775 {
7776 /* inform the direct session about changes */
7777
7778 ComObjPtr <Machine> that = this;
7779 alock.leave();
7780
7781 if (sharedFoldersChanged)
7782 that->onSharedFolderChange();
7783
7784 if (vrdpChanged)
7785 that->onVRDPServerChange();
7786 if (dvdChanged)
7787 that->onDVDDriveChange();
7788 if (floppyChanged)
7789 that->onFloppyDriveChange();
7790 if (usbChanged)
7791 that->onUSBControllerChange();
7792 if (sataChanged)
7793 that->onSATAControllerChange();
7794
7795 for (ULONG slot = 0; slot < ELEMENTS (networkAdapters); slot ++)
7796 if (networkAdapters [slot])
7797 that->onNetworkAdapterChange (networkAdapters [slot]);
7798 for (ULONG slot = 0; slot < ELEMENTS (serialPorts); slot ++)
7799 if (serialPorts [slot])
7800 that->onSerialPortChange (serialPorts [slot]);
7801 for (ULONG slot = 0; slot < ELEMENTS (parallelPorts); slot ++)
7802 if (parallelPorts [slot])
7803 that->onParallelPortChange (parallelPorts [slot]);
7804 }
7805}
7806
7807/**
7808 * Commits all the changes to machine settings.
7809 *
7810 * Note that when committing fails at some stage, it still continues
7811 * until the end. So, all data will either be actually committed or rolled
7812 * back (for failed cases) and the returned result code will describe the
7813 * first failure encountered. However, #isModified() will still return true
7814 * in case of failure, to indicade that settings in memory and on disk are
7815 * out of sync.
7816 *
7817 * @note Locks objects!
7818 */
7819HRESULT Machine::commit()
7820{
7821 AutoCaller autoCaller (this);
7822 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
7823
7824 AutoWriteLock alock (this);
7825
7826 HRESULT rc = S_OK;
7827
7828 /*
7829 * use safe commit to ensure Snapshot machines (that share mUserData)
7830 * will still refer to a valid memory location
7831 */
7832 mUserData.commitCopy();
7833
7834 mHWData.commit();
7835
7836 if (mHDData.isBackedUp())
7837 rc = fixupHardDisks (true /* aCommit */);
7838
7839 mBIOSSettings->commit();
7840#ifdef VBOX_WITH_VRDP
7841 mVRDPServer->commit();
7842#endif
7843 mDVDDrive->commit();
7844 mFloppyDrive->commit();
7845 mAudioAdapter->commit();
7846 mUSBController->commit();
7847 mSATAController->commit();
7848
7849 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
7850 mNetworkAdapters [slot]->commit();
7851 for (ULONG slot = 0; slot < ELEMENTS (mSerialPorts); slot ++)
7852 mSerialPorts [slot]->commit();
7853 for (ULONG slot = 0; slot < ELEMENTS (mParallelPorts); slot ++)
7854 mParallelPorts [slot]->commit();
7855
7856 if (mType == IsSessionMachine)
7857 {
7858 /* attach new data to the primary machine and reshare it */
7859 mPeer->mUserData.attach (mUserData);
7860 mPeer->mHWData.attach (mHWData);
7861 mPeer->mHDData.attach (mHDData);
7862 }
7863
7864 if (FAILED (rc))
7865 {
7866 /*
7867 * backup arbitrary data item to cause #isModified() to still return
7868 * true in case of any error
7869 */
7870 mHWData.backup();
7871 }
7872
7873 return rc;
7874}
7875
7876/**
7877 * Copies all the hardware data from the given machine.
7878 *
7879 * @note
7880 * This method must be called from under this object's lock.
7881 * @note
7882 * This method doesn't call #commit(), so all data remains backed up
7883 * and unsaved.
7884 */
7885void Machine::copyFrom (Machine *aThat)
7886{
7887 AssertReturn (mType == IsMachine || mType == IsSessionMachine, (void) 0);
7888 AssertReturn (aThat->mType == IsSnapshotMachine, (void) 0);
7889
7890 mHWData.assignCopy (aThat->mHWData);
7891
7892 // create copies of all shared folders (mHWData after attiching a copy
7893 // contains just references to original objects)
7894 for (HWData::SharedFolderList::iterator it = mHWData->mSharedFolders.begin();
7895 it != mHWData->mSharedFolders.end();
7896 ++ it)
7897 {
7898 ComObjPtr <SharedFolder> folder;
7899 folder.createObject();
7900 HRESULT rc = folder->initCopy (machine(), *it);
7901 AssertComRC (rc);
7902 *it = folder;
7903 }
7904
7905 mBIOSSettings->copyFrom (aThat->mBIOSSettings);
7906#ifdef VBOX_WITH_VRDP
7907 mVRDPServer->copyFrom (aThat->mVRDPServer);
7908#endif
7909 mDVDDrive->copyFrom (aThat->mDVDDrive);
7910 mFloppyDrive->copyFrom (aThat->mFloppyDrive);
7911 mAudioAdapter->copyFrom (aThat->mAudioAdapter);
7912 mUSBController->copyFrom (aThat->mUSBController);
7913 mSATAController->copyFrom (aThat->mSATAController);
7914
7915 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
7916 mNetworkAdapters [slot]->copyFrom (aThat->mNetworkAdapters [slot]);
7917 for (ULONG slot = 0; slot < ELEMENTS (mSerialPorts); slot ++)
7918 mSerialPorts [slot]->copyFrom (aThat->mSerialPorts [slot]);
7919 for (ULONG slot = 0; slot < ELEMENTS (mParallelPorts); slot ++)
7920 mParallelPorts [slot]->copyFrom (aThat->mParallelPorts [slot]);
7921}
7922
7923#ifdef VBOX_WITH_RESOURCE_USAGE_API
7924void Machine::registerMetrics (PerformanceCollector *aCollector, Machine *aMachine, RTPROCESS pid)
7925{
7926 pm::CollectorHAL *hal = aCollector->getHAL();
7927 /* Create sub metrics */
7928 pm::SubMetric *cpuLoadUser = new pm::SubMetric ("CPU/Load/User",
7929 "Percentage of processor time spent in user mode by VM process.");
7930 pm::SubMetric *cpuLoadKernel = new pm::SubMetric ("CPU/Load/Kernel",
7931 "Percentage of processor time spent in kernel mode by VM process.");
7932 pm::SubMetric *ramUsageUsed = new pm::SubMetric ("RAM/Usage/Used",
7933 "Size of resident portion of VM process in memory.");
7934 /* Create and register base metrics */
7935 IUnknown *objptr;
7936
7937 ComObjPtr<Machine> tmp = aMachine;
7938 tmp.queryInterfaceTo (&objptr);
7939 pm::BaseMetric *cpuLoad = new pm::MachineCpuLoadRaw (hal, objptr, pid,
7940 cpuLoadUser, cpuLoadKernel);
7941 aCollector->registerBaseMetric (cpuLoad);
7942 pm::BaseMetric *ramUsage = new pm::MachineRamUsage (hal, objptr, pid,
7943 ramUsageUsed);
7944 aCollector->registerBaseMetric (ramUsage);
7945
7946 aCollector->registerMetric (new pm::Metric(cpuLoad, cpuLoadUser, 0));
7947 aCollector->registerMetric (new pm::Metric(cpuLoad, cpuLoadUser,
7948 new pm::AggregateAvg()));
7949 aCollector->registerMetric (new pm::Metric(cpuLoad, cpuLoadUser,
7950 new pm::AggregateMin()));
7951 aCollector->registerMetric (new pm::Metric(cpuLoad, cpuLoadUser,
7952 new pm::AggregateMax()));
7953 aCollector->registerMetric (new pm::Metric(cpuLoad, cpuLoadKernel, 0));
7954 aCollector->registerMetric (new pm::Metric(cpuLoad, cpuLoadKernel,
7955 new pm::AggregateAvg()));
7956 aCollector->registerMetric (new pm::Metric(cpuLoad, cpuLoadKernel,
7957 new pm::AggregateMin()));
7958 aCollector->registerMetric (new pm::Metric(cpuLoad, cpuLoadKernel,
7959 new pm::AggregateMax()));
7960
7961 aCollector->registerMetric (new pm::Metric(ramUsage, ramUsageUsed, 0));
7962 aCollector->registerMetric (new pm::Metric(ramUsage, ramUsageUsed,
7963 new pm::AggregateAvg()));
7964 aCollector->registerMetric (new pm::Metric(ramUsage, ramUsageUsed,
7965 new pm::AggregateMin()));
7966 aCollector->registerMetric (new pm::Metric(ramUsage, ramUsageUsed,
7967 new pm::AggregateMax()));
7968};
7969
7970void Machine::unregisterMetrics (PerformanceCollector *aCollector, Machine *aMachine)
7971{
7972 aCollector->unregisterMetricsFor (aMachine);
7973 aCollector->unregisterBaseMetricsFor (aMachine);
7974};
7975#endif /* VBOX_WITH_RESOURCE_USAGE_API */
7976
7977
7978/////////////////////////////////////////////////////////////////////////////
7979// SessionMachine class
7980/////////////////////////////////////////////////////////////////////////////
7981
7982/** Task structure for asynchronous VM operations */
7983struct SessionMachine::Task
7984{
7985 Task (SessionMachine *m, Progress *p)
7986 : machine (m), progress (p)
7987 , state (m->mData->mMachineState) // save the current machine state
7988 , subTask (false), settingsChanged (false)
7989 {}
7990
7991 void modifyLastState (MachineState_T s)
7992 {
7993 *const_cast <MachineState_T *> (&state) = s;
7994 }
7995
7996 virtual void handler() = 0;
7997
7998 const ComObjPtr <SessionMachine> machine;
7999 const ComObjPtr <Progress> progress;
8000 const MachineState_T state;
8001
8002 bool subTask : 1;
8003 bool settingsChanged : 1;
8004};
8005
8006/** Take snapshot task */
8007struct SessionMachine::TakeSnapshotTask : public SessionMachine::Task
8008{
8009 TakeSnapshotTask (SessionMachine *m)
8010 : Task (m, NULL) {}
8011
8012 void handler() { machine->takeSnapshotHandler (*this); }
8013};
8014
8015/** Discard snapshot task */
8016struct SessionMachine::DiscardSnapshotTask : public SessionMachine::Task
8017{
8018 DiscardSnapshotTask (SessionMachine *m, Progress *p, Snapshot *s)
8019 : Task (m, p)
8020 , snapshot (s) {}
8021
8022 DiscardSnapshotTask (const Task &task, Snapshot *s)
8023 : Task (task)
8024 , snapshot (s) {}
8025
8026 void handler() { machine->discardSnapshotHandler (*this); }
8027
8028 const ComObjPtr <Snapshot> snapshot;
8029};
8030
8031/** Discard current state task */
8032struct SessionMachine::DiscardCurrentStateTask : public SessionMachine::Task
8033{
8034 DiscardCurrentStateTask (SessionMachine *m, Progress *p,
8035 bool discardCurSnapshot)
8036 : Task (m, p), discardCurrentSnapshot (discardCurSnapshot) {}
8037
8038 void handler() { machine->discardCurrentStateHandler (*this); }
8039
8040 const bool discardCurrentSnapshot;
8041};
8042
8043////////////////////////////////////////////////////////////////////////////////
8044
8045DEFINE_EMPTY_CTOR_DTOR (SessionMachine)
8046
8047HRESULT SessionMachine::FinalConstruct()
8048{
8049 LogFlowThisFunc (("\n"));
8050
8051 /* set the proper type to indicate we're the SessionMachine instance */
8052 unconst (mType) = IsSessionMachine;
8053
8054#if defined(RT_OS_WINDOWS)
8055 mIPCSem = NULL;
8056#elif defined(RT_OS_OS2)
8057 mIPCSem = NULLHANDLE;
8058#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
8059 mIPCSem = -1;
8060#else
8061# error "Port me!"
8062#endif
8063
8064 return S_OK;
8065}
8066
8067void SessionMachine::FinalRelease()
8068{
8069 LogFlowThisFunc (("\n"));
8070
8071 uninit (Uninit::Unexpected);
8072}
8073
8074/**
8075 * @note Must be called only by Machine::openSession() from its own write lock.
8076 */
8077HRESULT SessionMachine::init (Machine *aMachine)
8078{
8079 LogFlowThisFuncEnter();
8080 LogFlowThisFunc (("mName={%ls}\n", aMachine->mUserData->mName.raw()));
8081
8082 AssertReturn (aMachine, E_INVALIDARG);
8083
8084 AssertReturn (aMachine->lockHandle()->isWriteLockOnCurrentThread(), E_FAIL);
8085
8086 /* Enclose the state transition NotReady->InInit->Ready */
8087 AutoInitSpan autoInitSpan (this);
8088 AssertReturn (autoInitSpan.isOk(), E_UNEXPECTED);
8089
8090 /* create the interprocess semaphore */
8091#if defined(RT_OS_WINDOWS)
8092 mIPCSemName = aMachine->mData->mConfigFileFull;
8093 for (size_t i = 0; i < mIPCSemName.length(); i++)
8094 if (mIPCSemName[i] == '\\')
8095 mIPCSemName[i] = '/';
8096 mIPCSem = ::CreateMutex (NULL, FALSE, mIPCSemName);
8097 ComAssertMsgRet (mIPCSem,
8098 ("Cannot create IPC mutex '%ls', err=%d\n",
8099 mIPCSemName.raw(), ::GetLastError()),
8100 E_FAIL);
8101#elif defined(RT_OS_OS2)
8102 Utf8Str ipcSem = Utf8StrFmt ("\\SEM32\\VBOX\\VM\\{%Vuuid}",
8103 aMachine->mData->mUuid.raw());
8104 mIPCSemName = ipcSem;
8105 APIRET arc = ::DosCreateMutexSem ((PSZ) ipcSem.raw(), &mIPCSem, 0, FALSE);
8106 ComAssertMsgRet (arc == NO_ERROR,
8107 ("Cannot create IPC mutex '%s', arc=%ld\n",
8108 ipcSem.raw(), arc),
8109 E_FAIL);
8110#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
8111 Utf8Str configFile = aMachine->mData->mConfigFileFull;
8112 char *configFileCP = NULL;
8113 int error;
8114 RTStrUtf8ToCurrentCP (&configFileCP, configFile);
8115 key_t key = ::ftok (configFileCP, 0);
8116 RTStrFree (configFileCP);
8117 mIPCSem = ::semget (key, 1, S_IRWXU | S_IRWXG | S_IRWXO | IPC_CREAT);
8118 error = errno;
8119 if (mIPCSem < 0 && error == ENOSYS)
8120 {
8121 setError(E_FAIL,
8122 tr ("Cannot create IPC semaphore. Most likely your host kernel lacks "
8123 "support for SysV IPC. Check the host kernel configuration for "
8124 "CONFIG_SYSVIPC=y"));
8125 return E_FAIL;
8126 }
8127 ComAssertMsgRet (mIPCSem >= 0, ("Cannot create IPC semaphore, errno=%d", error),
8128 E_FAIL);
8129 /* set the initial value to 1 */
8130 int rv = ::semctl (mIPCSem, 0, SETVAL, 1);
8131 ComAssertMsgRet (rv == 0, ("Cannot init IPC semaphore, errno=%d", errno),
8132 E_FAIL);
8133#else
8134# error "Port me!"
8135#endif
8136
8137 /* memorize the peer Machine */
8138 unconst (mPeer) = aMachine;
8139 /* share the parent pointer */
8140 unconst (mParent) = aMachine->mParent;
8141
8142 /* take the pointers to data to share */
8143 mData.share (aMachine->mData);
8144 mSSData.share (aMachine->mSSData);
8145
8146 mUserData.share (aMachine->mUserData);
8147 mHWData.share (aMachine->mHWData);
8148 mHDData.share (aMachine->mHDData);
8149
8150 unconst (mBIOSSettings).createObject();
8151 mBIOSSettings->init (this, aMachine->mBIOSSettings);
8152#ifdef VBOX_WITH_VRDP
8153 /* create another VRDPServer object that will be mutable */
8154 unconst (mVRDPServer).createObject();
8155 mVRDPServer->init (this, aMachine->mVRDPServer);
8156#endif
8157 /* create another DVD drive object that will be mutable */
8158 unconst (mDVDDrive).createObject();
8159 mDVDDrive->init (this, aMachine->mDVDDrive);
8160 /* create another floppy drive object that will be mutable */
8161 unconst (mFloppyDrive).createObject();
8162 mFloppyDrive->init (this, aMachine->mFloppyDrive);
8163 /* create another audio adapter object that will be mutable */
8164 unconst (mAudioAdapter).createObject();
8165 mAudioAdapter->init (this, aMachine->mAudioAdapter);
8166 /* create a list of serial ports that will be mutable */
8167 for (ULONG slot = 0; slot < ELEMENTS (mSerialPorts); slot ++)
8168 {
8169 unconst (mSerialPorts [slot]).createObject();
8170 mSerialPorts [slot]->init (this, aMachine->mSerialPorts [slot]);
8171 }
8172 /* create a list of parallel ports that will be mutable */
8173 for (ULONG slot = 0; slot < ELEMENTS (mParallelPorts); slot ++)
8174 {
8175 unconst (mParallelPorts [slot]).createObject();
8176 mParallelPorts [slot]->init (this, aMachine->mParallelPorts [slot]);
8177 }
8178 /* create another USB controller object that will be mutable */
8179 unconst (mUSBController).createObject();
8180 mUSBController->init (this, aMachine->mUSBController);
8181 /* create another SATA controller object that will be mutable */
8182 unconst (mSATAController).createObject();
8183 mSATAController->init (this, aMachine->mSATAController);
8184 /* create a list of network adapters that will be mutable */
8185 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
8186 {
8187 unconst (mNetworkAdapters [slot]).createObject();
8188 mNetworkAdapters [slot]->init (this, aMachine->mNetworkAdapters [slot]);
8189 }
8190
8191 /* Confirm a successful initialization when it's the case */
8192 autoInitSpan.setSucceeded();
8193
8194 LogFlowThisFuncLeave();
8195 return S_OK;
8196}
8197
8198/**
8199 * Uninitializes this session object. If the reason is other than
8200 * Uninit::Unexpected, then this method MUST be called from #checkForDeath().
8201 *
8202 * @param aReason uninitialization reason
8203 *
8204 * @note Locks mParent + this object for writing.
8205 */
8206void SessionMachine::uninit (Uninit::Reason aReason)
8207{
8208 LogFlowThisFuncEnter();
8209 LogFlowThisFunc (("reason=%d\n", aReason));
8210
8211 /*
8212 * Strongly reference ourselves to prevent this object deletion after
8213 * mData->mSession.mMachine.setNull() below (which can release the last
8214 * reference and call the destructor). Important: this must be done before
8215 * accessing any members (and before AutoUninitSpan that does it as well).
8216 * This self reference will be released as the very last step on return.
8217 */
8218 ComObjPtr <SessionMachine> selfRef = this;
8219
8220 /* Enclose the state transition Ready->InUninit->NotReady */
8221 AutoUninitSpan autoUninitSpan (this);
8222 if (autoUninitSpan.uninitDone())
8223 {
8224 LogFlowThisFunc (("Already uninitialized\n"));
8225 LogFlowThisFuncLeave();
8226 return;
8227 }
8228
8229 if (autoUninitSpan.initFailed())
8230 {
8231 /* We've been called by init() because it's failed. It's not really
8232 * necessary (nor it's safe) to perform the regular uninit sequense
8233 * below, the following is enough.
8234 */
8235 LogFlowThisFunc (("Initialization failed.\n"));
8236#if defined(RT_OS_WINDOWS)
8237 if (mIPCSem)
8238 ::CloseHandle (mIPCSem);
8239 mIPCSem = NULL;
8240#elif defined(RT_OS_OS2)
8241 if (mIPCSem != NULLHANDLE)
8242 ::DosCloseMutexSem (mIPCSem);
8243 mIPCSem = NULLHANDLE;
8244#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
8245 if (mIPCSem >= 0)
8246 ::semctl (mIPCSem, 0, IPC_RMID);
8247 mIPCSem = -1;
8248#else
8249# error "Port me!"
8250#endif
8251 uninitDataAndChildObjects();
8252 mData.free();
8253 unconst (mParent).setNull();
8254 unconst (mPeer).setNull();
8255 LogFlowThisFuncLeave();
8256 return;
8257 }
8258
8259 /* We need to lock this object in uninit() because the lock is shared
8260 * with mPeer (as well as data we modify below). mParent->addProcessToReap()
8261 * and others need mParent lock. */
8262 AutoMultiWriteLock2 alock (mParent, this);
8263
8264#ifdef VBOX_WITH_RESOURCE_USAGE_API
8265 unregisterMetrics (mParent->performanceCollector(), mPeer);
8266#endif /* VBOX_WITH_RESOURCE_USAGE_API */
8267
8268 MachineState_T lastState = mData->mMachineState;
8269
8270 if (aReason == Uninit::Abnormal)
8271 {
8272 LogWarningThisFunc (("ABNORMAL client termination! (wasRunning=%d)\n",
8273 lastState >= MachineState_Running));
8274
8275 /* reset the state to Aborted */
8276 if (mData->mMachineState != MachineState_Aborted)
8277 setMachineState (MachineState_Aborted);
8278 }
8279
8280 if (isModified())
8281 {
8282 LogWarningThisFunc (("Discarding unsaved settings changes!\n"));
8283 rollback (false /* aNotify */);
8284 }
8285
8286 Assert (!mSnapshotData.mStateFilePath || !mSnapshotData.mSnapshot);
8287 if (mSnapshotData.mStateFilePath)
8288 {
8289 LogWarningThisFunc (("canceling failed save state request!\n"));
8290 endSavingState (FALSE /* aSuccess */);
8291 }
8292 else if (!!mSnapshotData.mSnapshot)
8293 {
8294 LogWarningThisFunc (("canceling untaken snapshot!\n"));
8295 endTakingSnapshot (FALSE /* aSuccess */);
8296 }
8297
8298#ifdef VBOX_WITH_USB
8299 /* release all captured USB devices */
8300 if (aReason == Uninit::Abnormal && lastState >= MachineState_Running)
8301 {
8302 /* Console::captureUSBDevices() is called in the VM process only after
8303 * setting the machine state to Starting or Restoring.
8304 * Console::detachAllUSBDevices() will be called upon successful
8305 * termination. So, we need to release USB devices only if there was
8306 * an abnormal termination of a running VM.
8307 *
8308 * This is identical to SessionMachine::DetachAllUSBDevices except
8309 * for the aAbnormal argument. */
8310 HRESULT rc = mUSBController->notifyProxy (false /* aInsertFilters */);
8311 AssertComRC (rc);
8312 NOREF (rc);
8313
8314 USBProxyService *service = mParent->host()->usbProxyService();
8315 if (service)
8316 service->detachAllDevicesFromVM (this, true /* aDone */, true /* aAbnormal */);
8317 }
8318#endif /* VBOX_WITH_USB */
8319
8320 if (!mData->mSession.mType.isNull())
8321 {
8322 /* mType is not null when this machine's process has been started by
8323 * VirtualBox::OpenRemoteSession(), therefore it is our child. We
8324 * need to queue the PID to reap the process (and avoid zombies on
8325 * Linux). */
8326 Assert (mData->mSession.mPid != NIL_RTPROCESS);
8327 mParent->addProcessToReap (mData->mSession.mPid);
8328 }
8329
8330 mData->mSession.mPid = NIL_RTPROCESS;
8331
8332 if (aReason == Uninit::Unexpected)
8333 {
8334 /* Uninitialization didn't come from #checkForDeath(), so tell the
8335 * client watcher thread to update the set of machines that have open
8336 * sessions. */
8337 mParent->updateClientWatcher();
8338 }
8339
8340 /* uninitialize all remote controls */
8341 if (mData->mSession.mRemoteControls.size())
8342 {
8343 LogFlowThisFunc (("Closing remote sessions (%d):\n",
8344 mData->mSession.mRemoteControls.size()));
8345
8346 Data::Session::RemoteControlList::iterator it =
8347 mData->mSession.mRemoteControls.begin();
8348 while (it != mData->mSession.mRemoteControls.end())
8349 {
8350 LogFlowThisFunc ((" Calling remoteControl->Uninitialize()...\n"));
8351 HRESULT rc = (*it)->Uninitialize();
8352 LogFlowThisFunc ((" remoteControl->Uninitialize() returned %08X\n", rc));
8353 if (FAILED (rc))
8354 LogWarningThisFunc (("Forgot to close the remote session?\n"));
8355 ++ it;
8356 }
8357 mData->mSession.mRemoteControls.clear();
8358 }
8359
8360 /*
8361 * An expected uninitialization can come only from #checkForDeath().
8362 * Otherwise it means that something's got really wrong (for examlple,
8363 * the Session implementation has released the VirtualBox reference
8364 * before it triggered #OnSessionEnd(), or before releasing IPC semaphore,
8365 * etc). However, it's also possible, that the client releases the IPC
8366 * semaphore correctly (i.e. before it releases the VirtualBox reference),
8367 * but but the VirtualBox release event comes first to the server process.
8368 * This case is practically possible, so we should not assert on an
8369 * unexpected uninit, just log a warning.
8370 */
8371
8372 if ((aReason == Uninit::Unexpected))
8373 LogWarningThisFunc (("Unexpected SessionMachine uninitialization!\n"));
8374
8375 if (aReason != Uninit::Normal)
8376 {
8377 mData->mSession.mDirectControl.setNull();
8378 }
8379 else
8380 {
8381 /* this must be null here (see #OnSessionEnd()) */
8382 Assert (mData->mSession.mDirectControl.isNull());
8383 Assert (mData->mSession.mState == SessionState_Closing);
8384 Assert (!mData->mSession.mProgress.isNull());
8385
8386 mData->mSession.mProgress->notifyComplete (S_OK);
8387 mData->mSession.mProgress.setNull();
8388 }
8389
8390 /* remove the association between the peer machine and this session machine */
8391 Assert (mData->mSession.mMachine == this ||
8392 aReason == Uninit::Unexpected);
8393
8394 /* reset the rest of session data */
8395 mData->mSession.mMachine.setNull();
8396 mData->mSession.mState = SessionState_Closed;
8397 mData->mSession.mType.setNull();
8398
8399 /* close the interprocess semaphore before leaving the shared lock */
8400#if defined(RT_OS_WINDOWS)
8401 if (mIPCSem)
8402 ::CloseHandle (mIPCSem);
8403 mIPCSem = NULL;
8404#elif defined(RT_OS_OS2)
8405 if (mIPCSem != NULLHANDLE)
8406 ::DosCloseMutexSem (mIPCSem);
8407 mIPCSem = NULLHANDLE;
8408#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
8409 if (mIPCSem >= 0)
8410 ::semctl (mIPCSem, 0, IPC_RMID);
8411 mIPCSem = -1;
8412#else
8413# error "Port me!"
8414#endif
8415
8416 /* fire an event */
8417 mParent->onSessionStateChange (mData->mUuid, SessionState_Closed);
8418
8419 uninitDataAndChildObjects();
8420
8421 /* free the essential data structure last */
8422 mData.free();
8423
8424 /* leave the shared lock before setting the below two to NULL */
8425 alock.leave();
8426
8427 unconst (mParent).setNull();
8428 unconst (mPeer).setNull();
8429
8430 LogFlowThisFuncLeave();
8431}
8432
8433// util::Lockable interface
8434////////////////////////////////////////////////////////////////////////////////
8435
8436/**
8437 * Overrides VirtualBoxBase::lockHandle() in order to share the lock handle
8438 * with the primary Machine instance (mPeer).
8439 */
8440RWLockHandle *SessionMachine::lockHandle() const
8441{
8442 AssertReturn (!mPeer.isNull(), NULL);
8443 return mPeer->lockHandle();
8444}
8445
8446// IInternalMachineControl methods
8447////////////////////////////////////////////////////////////////////////////////
8448
8449/**
8450 * @note Locks the same as #setMachineState() does.
8451 */
8452STDMETHODIMP SessionMachine::UpdateState (MachineState_T machineState)
8453{
8454 return setMachineState (machineState);
8455}
8456
8457/**
8458 * @note Locks this object for reading.
8459 */
8460STDMETHODIMP SessionMachine::GetIPCId (BSTR *id)
8461{
8462 AutoCaller autoCaller (this);
8463 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8464
8465 AutoReadLock alock (this);
8466
8467#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
8468 mIPCSemName.cloneTo (id);
8469 return S_OK;
8470#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
8471 mData->mConfigFileFull.cloneTo (id);
8472 return S_OK;
8473#else
8474# error "Port me!"
8475#endif
8476}
8477
8478/**
8479 * Goes through the USB filters of the given machine to see if the given
8480 * device matches any filter or not.
8481 *
8482 * @note Locks the same as USBController::hasMatchingFilter() does.
8483 */
8484STDMETHODIMP SessionMachine::RunUSBDeviceFilters (IUSBDevice *aUSBDevice,
8485 BOOL *aMatched,
8486 ULONG *aMaskedIfs)
8487{
8488 LogFlowThisFunc (("\n"));
8489
8490 if (!aUSBDevice)
8491 return E_INVALIDARG;
8492 if (!aMatched)
8493 return E_POINTER;
8494
8495 AutoCaller autoCaller (this);
8496 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8497
8498#ifdef VBOX_WITH_USB
8499 *aMatched = mUSBController->hasMatchingFilter (aUSBDevice, aMaskedIfs);
8500#else
8501 *aMatched = FALSE;
8502#endif
8503
8504 return S_OK;
8505}
8506
8507/**
8508 * @note Locks the same as Host::captureUSBDevice() does.
8509 */
8510STDMETHODIMP SessionMachine::CaptureUSBDevice (INPTR GUIDPARAM aId)
8511{
8512 LogFlowThisFunc (("\n"));
8513
8514 AutoCaller autoCaller (this);
8515 AssertComRCReturnRC (autoCaller.rc());
8516
8517#ifdef VBOX_WITH_USB
8518 /* if captureDeviceForVM() fails, it must have set extended error info */
8519 MultiResult rc = mParent->host()->checkUSBProxyService();
8520 CheckComRCReturnRC (rc);
8521
8522 USBProxyService *service = mParent->host()->usbProxyService();
8523 AssertReturn (service, E_FAIL);
8524 return service->captureDeviceForVM (this, aId);
8525#else
8526 return E_FAIL;
8527#endif
8528}
8529
8530/**
8531 * @note Locks the same as Host::detachUSBDevice() does.
8532 */
8533STDMETHODIMP SessionMachine::DetachUSBDevice (INPTR GUIDPARAM aId, BOOL aDone)
8534{
8535 LogFlowThisFunc (("\n"));
8536
8537 AutoCaller autoCaller (this);
8538 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8539
8540#ifdef VBOX_WITH_USB
8541 USBProxyService *service = mParent->host()->usbProxyService();
8542 AssertReturn (service, E_FAIL);
8543 return service->detachDeviceFromVM (this, aId, !!aDone);
8544#else
8545 return E_FAIL;
8546#endif
8547}
8548
8549/**
8550 * Inserts all machine filters to the USB proxy service and then calls
8551 * Host::autoCaptureUSBDevices().
8552 *
8553 * Called by Console from the VM process upon VM startup.
8554 *
8555 * @note Locks what called methods lock.
8556 */
8557STDMETHODIMP SessionMachine::AutoCaptureUSBDevices()
8558{
8559 LogFlowThisFunc (("\n"));
8560
8561 AutoCaller autoCaller (this);
8562 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8563
8564#ifdef VBOX_WITH_USB
8565 HRESULT rc = mUSBController->notifyProxy (true /* aInsertFilters */);
8566 AssertComRC (rc);
8567 NOREF (rc);
8568
8569 USBProxyService *service = mParent->host()->usbProxyService();
8570 AssertReturn (service, E_FAIL);
8571 return service->autoCaptureDevicesForVM (this);
8572#else
8573 return S_OK;
8574#endif
8575}
8576
8577/**
8578 * Removes all machine filters from the USB proxy service and then calls
8579 * Host::detachAllUSBDevices().
8580 *
8581 * Called by Console from the VM process upon normal VM termination or by
8582 * SessionMachine::uninit() upon abnormal VM termination (from under the
8583 * Machine/SessionMachine lock).
8584 *
8585 * @note Locks what called methods lock.
8586 */
8587STDMETHODIMP SessionMachine::DetachAllUSBDevices(BOOL aDone)
8588{
8589 LogFlowThisFunc (("\n"));
8590
8591 AutoCaller autoCaller (this);
8592 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8593
8594#ifdef VBOX_WITH_USB
8595 HRESULT rc = mUSBController->notifyProxy (false /* aInsertFilters */);
8596 AssertComRC (rc);
8597 NOREF (rc);
8598
8599 USBProxyService *service = mParent->host()->usbProxyService();
8600 AssertReturn (service, E_FAIL);
8601 return service->detachAllDevicesFromVM (this, !!aDone, false /* aAbnormal */);
8602#else
8603 return S_OK;
8604#endif
8605}
8606
8607/**
8608 * @note Locks mParent + this object for writing.
8609 */
8610STDMETHODIMP SessionMachine::OnSessionEnd (ISession *aSession,
8611 IProgress **aProgress)
8612{
8613 LogFlowThisFuncEnter();
8614
8615 AssertReturn (aSession, E_INVALIDARG);
8616 AssertReturn (aProgress, E_INVALIDARG);
8617
8618 AutoCaller autoCaller (this);
8619
8620 LogFlowThisFunc (("state=%d\n", autoCaller.state()));
8621 /*
8622 * We don't assert below because it might happen that a non-direct session
8623 * informs us it is closed right after we've been uninitialized -- it's ok.
8624 */
8625 CheckComRCReturnRC (autoCaller.rc());
8626
8627 /* get IInternalSessionControl interface */
8628 ComPtr <IInternalSessionControl> control (aSession);
8629
8630 ComAssertRet (!control.isNull(), E_INVALIDARG);
8631
8632 /* Progress::init() needs mParent lock */
8633 AutoMultiWriteLock2 alock (mParent, this);
8634
8635 if (control.equalsTo (mData->mSession.mDirectControl))
8636 {
8637 ComAssertRet (aProgress, E_POINTER);
8638
8639 /* The direct session is being normally closed by the client process
8640 * ----------------------------------------------------------------- */
8641
8642 /* go to the closing state (essential for all open*Session() calls and
8643 * for #checkForDeath()) */
8644 Assert (mData->mSession.mState == SessionState_Open);
8645 mData->mSession.mState = SessionState_Closing;
8646
8647 /* set direct control to NULL to release the remote instance */
8648 mData->mSession.mDirectControl.setNull();
8649 LogFlowThisFunc (("Direct control is set to NULL\n"));
8650
8651 /*
8652 * Create the progress object the client will use to wait until
8653 * #checkForDeath() is called to uninitialize this session object
8654 * after it releases the IPC semaphore.
8655 */
8656 ComObjPtr <Progress> progress;
8657 progress.createObject();
8658 progress->init (mParent, static_cast <IMachine *> (mPeer),
8659 Bstr (tr ("Closing session")), FALSE /* aCancelable */);
8660 progress.queryInterfaceTo (aProgress);
8661 mData->mSession.mProgress = progress;
8662 }
8663 else
8664 {
8665 /* the remote session is being normally closed */
8666 Data::Session::RemoteControlList::iterator it =
8667 mData->mSession.mRemoteControls.begin();
8668 while (it != mData->mSession.mRemoteControls.end())
8669 {
8670 if (control.equalsTo (*it))
8671 break;
8672 ++it;
8673 }
8674 BOOL found = it != mData->mSession.mRemoteControls.end();
8675 ComAssertMsgRet (found, ("The session is not found in the session list!"),
8676 E_INVALIDARG);
8677 mData->mSession.mRemoteControls.remove (*it);
8678 }
8679
8680 LogFlowThisFuncLeave();
8681 return S_OK;
8682}
8683
8684/**
8685 * @note Locks mParent + this object for writing.
8686 */
8687STDMETHODIMP SessionMachine::BeginSavingState (IProgress *aProgress, BSTR *aStateFilePath)
8688{
8689 LogFlowThisFuncEnter();
8690
8691 AssertReturn (aProgress, E_INVALIDARG);
8692 AssertReturn (aStateFilePath, E_POINTER);
8693
8694 AutoCaller autoCaller (this);
8695 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8696
8697 /* mParent->addProgress() needs mParent lock */
8698 AutoMultiWriteLock2 alock (mParent, this);
8699
8700 AssertReturn (mData->mMachineState == MachineState_Paused &&
8701 mSnapshotData.mLastState == MachineState_Null &&
8702 mSnapshotData.mProgressId.isEmpty() &&
8703 mSnapshotData.mStateFilePath.isNull(),
8704 E_FAIL);
8705
8706 /* memorize the progress ID and add it to the global collection */
8707 Guid progressId;
8708 HRESULT rc = aProgress->COMGETTER(Id) (progressId.asOutParam());
8709 AssertComRCReturn (rc, rc);
8710 rc = mParent->addProgress (aProgress);
8711 AssertComRCReturn (rc, rc);
8712
8713 Bstr stateFilePath;
8714 /* stateFilePath is null when the machine is not running */
8715 if (mData->mMachineState == MachineState_Paused)
8716 {
8717 stateFilePath = Utf8StrFmt ("%ls%c{%Vuuid}.sav",
8718 mUserData->mSnapshotFolderFull.raw(),
8719 RTPATH_DELIMITER, mData->mUuid.raw());
8720 }
8721
8722 /* fill in the snapshot data */
8723 mSnapshotData.mLastState = mData->mMachineState;
8724 mSnapshotData.mProgressId = progressId;
8725 mSnapshotData.mStateFilePath = stateFilePath;
8726
8727 /* set the state to Saving (this is expected by Console::SaveState()) */
8728 setMachineState (MachineState_Saving);
8729
8730 stateFilePath.cloneTo (aStateFilePath);
8731
8732 return S_OK;
8733}
8734
8735/**
8736 * @note Locks mParent + this objects for writing.
8737 */
8738STDMETHODIMP SessionMachine::EndSavingState (BOOL aSuccess)
8739{
8740 LogFlowThisFunc (("\n"));
8741
8742 AutoCaller autoCaller (this);
8743 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8744
8745 /* endSavingState() need mParent lock */
8746 AutoMultiWriteLock2 alock (mParent, this);
8747
8748 AssertReturn (mData->mMachineState == MachineState_Saving &&
8749 mSnapshotData.mLastState != MachineState_Null &&
8750 !mSnapshotData.mProgressId.isEmpty() &&
8751 !mSnapshotData.mStateFilePath.isNull(),
8752 E_FAIL);
8753
8754 /*
8755 * on success, set the state to Saved;
8756 * on failure, set the state to the state we had when BeginSavingState() was
8757 * called (this is expected by Console::SaveState() and
8758 * Console::saveStateThread())
8759 */
8760 if (aSuccess)
8761 setMachineState (MachineState_Saved);
8762 else
8763 setMachineState (mSnapshotData.mLastState);
8764
8765 return endSavingState (aSuccess);
8766}
8767
8768/**
8769 * @note Locks this objects for writing.
8770 */
8771STDMETHODIMP SessionMachine::AdoptSavedState (INPTR BSTR aSavedStateFile)
8772{
8773 LogFlowThisFunc (("\n"));
8774
8775 AssertReturn (aSavedStateFile, E_INVALIDARG);
8776
8777 AutoCaller autoCaller (this);
8778 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8779
8780 AutoWriteLock alock (this);
8781
8782 AssertReturn (mData->mMachineState == MachineState_PoweredOff ||
8783 mData->mMachineState == MachineState_Aborted,
8784 E_FAIL);
8785
8786 Utf8Str stateFilePathFull = aSavedStateFile;
8787 int vrc = calculateFullPath (stateFilePathFull, stateFilePathFull);
8788 if (VBOX_FAILURE (vrc))
8789 return setError (E_FAIL,
8790 tr ("Invalid saved state file path: '%ls' (%Vrc)"),
8791 aSavedStateFile, vrc);
8792
8793 mSSData->mStateFilePath = stateFilePathFull;
8794
8795 /* The below setMachineState() will detect the state transition and will
8796 * update the settings file */
8797
8798 return setMachineState (MachineState_Saved);
8799}
8800
8801/**
8802 * @note Locks mParent + this objects for writing.
8803 */
8804STDMETHODIMP SessionMachine::BeginTakingSnapshot (
8805 IConsole *aInitiator, INPTR BSTR aName, INPTR BSTR aDescription,
8806 IProgress *aProgress, BSTR *aStateFilePath,
8807 IProgress **aServerProgress)
8808{
8809 LogFlowThisFuncEnter();
8810
8811 AssertReturn (aInitiator && aName, E_INVALIDARG);
8812 AssertReturn (aStateFilePath && aServerProgress, E_POINTER);
8813
8814 LogFlowThisFunc (("aName='%ls'\n", aName));
8815
8816 AutoCaller autoCaller (this);
8817 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8818
8819 /* Progress::init() needs mParent lock */
8820 AutoMultiWriteLock2 alock (mParent, this);
8821
8822 AssertReturn ((mData->mMachineState < MachineState_Running ||
8823 mData->mMachineState == MachineState_Paused) &&
8824 mSnapshotData.mLastState == MachineState_Null &&
8825 mSnapshotData.mSnapshot.isNull() &&
8826 mSnapshotData.mServerProgress.isNull() &&
8827 mSnapshotData.mCombinedProgress.isNull(),
8828 E_FAIL);
8829
8830 bool takingSnapshotOnline = mData->mMachineState == MachineState_Paused;
8831
8832 if (!takingSnapshotOnline && mData->mMachineState != MachineState_Saved)
8833 {
8834 /*
8835 * save all current settings to ensure current changes are committed
8836 * and hard disks are fixed up
8837 */
8838 HRESULT rc = saveSettings();
8839 CheckComRCReturnRC (rc);
8840 }
8841
8842 /* check that there are no Writethrough hard disks attached */
8843 for (HDData::HDAttachmentList::const_iterator
8844 it = mHDData->mHDAttachments.begin();
8845 it != mHDData->mHDAttachments.end();
8846 ++ it)
8847 {
8848 ComObjPtr <HardDisk> hd = (*it)->hardDisk();
8849 AutoWriteLock hdLock (hd);
8850 if (hd->type() == HardDiskType_Writethrough)
8851 return setError (E_FAIL,
8852 tr ("Cannot take a snapshot when there is a Writethrough hard "
8853 " disk attached ('%ls')"), hd->toString().raw());
8854 }
8855
8856 AssertReturn (aProgress || !takingSnapshotOnline, E_FAIL);
8857
8858 /* create an ID for the snapshot */
8859 Guid snapshotId;
8860 snapshotId.create();
8861
8862 Bstr stateFilePath;
8863 /* stateFilePath is null when the machine is not online nor saved */
8864 if (takingSnapshotOnline || mData->mMachineState == MachineState_Saved)
8865 stateFilePath = Utf8StrFmt ("%ls%c{%Vuuid}.sav",
8866 mUserData->mSnapshotFolderFull.raw(),
8867 RTPATH_DELIMITER,
8868 snapshotId.ptr());
8869
8870 /* ensure the directory for the saved state file exists */
8871 if (stateFilePath)
8872 {
8873 Utf8Str dir = stateFilePath;
8874 RTPathStripFilename (dir.mutableRaw());
8875 if (!RTDirExists (dir))
8876 {
8877 int vrc = RTDirCreateFullPath (dir, 0777);
8878 if (VBOX_FAILURE (vrc))
8879 return setError (E_FAIL,
8880 tr ("Could not create a directory '%s' to save the "
8881 "VM state to (%Vrc)"),
8882 dir.raw(), vrc);
8883 }
8884 }
8885
8886 /* create a snapshot machine object */
8887 ComObjPtr <SnapshotMachine> snapshotMachine;
8888 snapshotMachine.createObject();
8889 HRESULT rc = snapshotMachine->init (this, snapshotId, stateFilePath);
8890 AssertComRCReturn (rc, rc);
8891
8892 Bstr progressDesc = Bstr (tr ("Taking snapshot of virtual machine"));
8893 Bstr firstOpDesc = Bstr (tr ("Preparing to take snapshot"));
8894
8895 /*
8896 * create a server-side progress object (it will be descriptionless
8897 * when we need to combine it with the VM-side progress, i.e. when we're
8898 * taking a snapshot online). The number of operations is:
8899 * 1 (preparing) + # of VDIs + 1 (if the state is saved so we need to copy it)
8900 */
8901 ComObjPtr <Progress> serverProgress;
8902 {
8903 ULONG opCount = 1 + mHDData->mHDAttachments.size();
8904 if (mData->mMachineState == MachineState_Saved)
8905 opCount ++;
8906 serverProgress.createObject();
8907 if (takingSnapshotOnline)
8908 rc = serverProgress->init (FALSE, opCount, firstOpDesc);
8909 else
8910 rc = serverProgress->init (mParent, aInitiator, progressDesc, FALSE,
8911 opCount, firstOpDesc);
8912 AssertComRCReturn (rc, rc);
8913 }
8914
8915 /* create a combined server-side progress object when necessary */
8916 ComObjPtr <CombinedProgress> combinedProgress;
8917 if (takingSnapshotOnline)
8918 {
8919 combinedProgress.createObject();
8920 rc = combinedProgress->init (mParent, aInitiator, progressDesc,
8921 serverProgress, aProgress);
8922 AssertComRCReturn (rc, rc);
8923 }
8924
8925 /* create a snapshot object */
8926 RTTIMESPEC time;
8927 ComObjPtr <Snapshot> snapshot;
8928 snapshot.createObject();
8929 rc = snapshot->init (snapshotId, aName, aDescription,
8930 *RTTimeNow (&time), snapshotMachine,
8931 mData->mCurrentSnapshot);
8932 AssertComRCReturnRC (rc);
8933
8934 /*
8935 * create and start the task on a separate thread
8936 * (note that it will not start working until we release alock)
8937 */
8938 TakeSnapshotTask *task = new TakeSnapshotTask (this);
8939 int vrc = RTThreadCreate (NULL, taskHandler,
8940 (void *) task,
8941 0, RTTHREADTYPE_MAIN_WORKER, 0, "TakeSnapshot");
8942 if (VBOX_FAILURE (vrc))
8943 {
8944 snapshot->uninit();
8945 delete task;
8946 ComAssertFailedRet (E_FAIL);
8947 }
8948
8949 /* fill in the snapshot data */
8950 mSnapshotData.mLastState = mData->mMachineState;
8951 mSnapshotData.mSnapshot = snapshot;
8952 mSnapshotData.mServerProgress = serverProgress;
8953 mSnapshotData.mCombinedProgress = combinedProgress;
8954
8955 /* set the state to Saving (this is expected by Console::TakeSnapshot()) */
8956 setMachineState (MachineState_Saving);
8957
8958 if (takingSnapshotOnline)
8959 stateFilePath.cloneTo (aStateFilePath);
8960 else
8961 *aStateFilePath = NULL;
8962
8963 serverProgress.queryInterfaceTo (aServerProgress);
8964
8965 LogFlowThisFuncLeave();
8966 return S_OK;
8967}
8968
8969/**
8970 * @note Locks mParent + this objects for writing.
8971 */
8972STDMETHODIMP SessionMachine::EndTakingSnapshot (BOOL aSuccess)
8973{
8974 LogFlowThisFunc (("\n"));
8975
8976 AutoCaller autoCaller (this);
8977 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
8978
8979 /* Lock mParent because of endTakingSnapshot() */
8980 AutoMultiWriteLock2 alock (mParent, this);
8981
8982 AssertReturn (!aSuccess ||
8983 (mData->mMachineState == MachineState_Saving &&
8984 mSnapshotData.mLastState != MachineState_Null &&
8985 !mSnapshotData.mSnapshot.isNull() &&
8986 !mSnapshotData.mServerProgress.isNull() &&
8987 !mSnapshotData.mCombinedProgress.isNull()),
8988 E_FAIL);
8989
8990 /*
8991 * set the state to the state we had when BeginTakingSnapshot() was called
8992 * (this is expected by Console::TakeSnapshot() and
8993 * Console::saveStateThread())
8994 */
8995 setMachineState (mSnapshotData.mLastState);
8996
8997 return endTakingSnapshot (aSuccess);
8998}
8999
9000/**
9001 * @note Locks mParent + this + children objects for writing!
9002 */
9003STDMETHODIMP SessionMachine::DiscardSnapshot (
9004 IConsole *aInitiator, INPTR GUIDPARAM aId,
9005 MachineState_T *aMachineState, IProgress **aProgress)
9006{
9007 LogFlowThisFunc (("\n"));
9008
9009 Guid id = aId;
9010 AssertReturn (aInitiator && !id.isEmpty(), E_INVALIDARG);
9011 AssertReturn (aMachineState && aProgress, E_POINTER);
9012
9013 AutoCaller autoCaller (this);
9014 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
9015
9016 /* Progress::init() needs mParent lock */
9017 AutoMultiWriteLock2 alock (mParent, this);
9018
9019 ComAssertRet (mData->mMachineState < MachineState_Running, E_FAIL);
9020
9021 ComObjPtr <Snapshot> snapshot;
9022 HRESULT rc = findSnapshot (id, snapshot, true /* aSetError */);
9023 CheckComRCReturnRC (rc);
9024
9025 AutoWriteLock snapshotLock (snapshot);
9026 if (snapshot == mData->mFirstSnapshot)
9027 {
9028 AutoWriteLock chLock (mData->mFirstSnapshot->childrenLock ());
9029 size_t childrenCount = mData->mFirstSnapshot->children().size();
9030 if (childrenCount > 1)
9031 return setError (E_FAIL,
9032 tr ("Cannot discard the snapshot '%ls' because it is the first "
9033 "snapshot of the machine '%ls' and it has more than one "
9034 "child snapshot (%d)"),
9035 snapshot->data().mName.raw(), mUserData->mName.raw(),
9036 childrenCount);
9037 }
9038
9039 /*
9040 * If the snapshot being discarded is the current one, ensure current
9041 * settings are committed and saved.
9042 */
9043 if (snapshot == mData->mCurrentSnapshot)
9044 {
9045 if (isModified())
9046 {
9047 rc = saveSettings();
9048 CheckComRCReturnRC (rc);
9049 }
9050 }
9051
9052 /*
9053 * create a progress object. The number of operations is:
9054 * 1 (preparing) + # of VDIs
9055 */
9056 ComObjPtr <Progress> progress;
9057 progress.createObject();
9058 rc = progress->init (mParent, aInitiator,
9059 Bstr (Utf8StrFmt (tr ("Discarding snapshot '%ls'"),
9060 snapshot->data().mName.raw())),
9061 FALSE /* aCancelable */,
9062 1 + snapshot->data().mMachine->mHDData->mHDAttachments.size(),
9063 Bstr (tr ("Preparing to discard snapshot")));
9064 AssertComRCReturn (rc, rc);
9065
9066 /* create and start the task on a separate thread */
9067 DiscardSnapshotTask *task = new DiscardSnapshotTask (this, progress, snapshot);
9068 int vrc = RTThreadCreate (NULL, taskHandler,
9069 (void *) task,
9070 0, RTTHREADTYPE_MAIN_WORKER, 0, "DiscardSnapshot");
9071 if (VBOX_FAILURE (vrc))
9072 delete task;
9073 ComAssertRCRet (vrc, E_FAIL);
9074
9075 /* set the proper machine state (note: after creating a Task instance) */
9076 setMachineState (MachineState_Discarding);
9077
9078 /* return the progress to the caller */
9079 progress.queryInterfaceTo (aProgress);
9080
9081 /* return the new state to the caller */
9082 *aMachineState = mData->mMachineState;
9083
9084 return S_OK;
9085}
9086
9087/**
9088 * @note Locks mParent + this + children objects for writing!
9089 */
9090STDMETHODIMP SessionMachine::DiscardCurrentState (
9091 IConsole *aInitiator, MachineState_T *aMachineState, IProgress **aProgress)
9092{
9093 LogFlowThisFunc (("\n"));
9094
9095 AssertReturn (aInitiator, E_INVALIDARG);
9096 AssertReturn (aMachineState && aProgress, E_POINTER);
9097
9098 AutoCaller autoCaller (this);
9099 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
9100
9101 /* Progress::init() needs mParent lock */
9102 AutoMultiWriteLock2 alock (mParent, this);
9103
9104 ComAssertRet (mData->mMachineState < MachineState_Running, E_FAIL);
9105
9106 if (mData->mCurrentSnapshot.isNull())
9107 return setError (E_FAIL,
9108 tr ("Could not discard the current state of the machine '%ls' "
9109 "because it doesn't have any snapshots"),
9110 mUserData->mName.raw());
9111
9112 /*
9113 * create a progress object. The number of operations is:
9114 * 1 (preparing) + # of VDIs + 1 (if we need to copy the saved state file)
9115 */
9116 ComObjPtr <Progress> progress;
9117 progress.createObject();
9118 {
9119 ULONG opCount = 1 + mData->mCurrentSnapshot->data()
9120 .mMachine->mHDData->mHDAttachments.size();
9121 if (mData->mCurrentSnapshot->stateFilePath())
9122 ++ opCount;
9123 progress->init (mParent, aInitiator,
9124 Bstr (tr ("Discarding current machine state")),
9125 FALSE /* aCancelable */, opCount,
9126 Bstr (tr ("Preparing to discard current state")));
9127 }
9128
9129 /* create and start the task on a separate thread */
9130 DiscardCurrentStateTask *task =
9131 new DiscardCurrentStateTask (this, progress, false /* discardCurSnapshot */);
9132 int vrc = RTThreadCreate (NULL, taskHandler,
9133 (void *) task,
9134 0, RTTHREADTYPE_MAIN_WORKER, 0, "DiscardCurState");
9135 if (VBOX_FAILURE (vrc))
9136 delete task;
9137 ComAssertRCRet (vrc, E_FAIL);
9138
9139 /* set the proper machine state (note: after creating a Task instance) */
9140 setMachineState (MachineState_Discarding);
9141
9142 /* return the progress to the caller */
9143 progress.queryInterfaceTo (aProgress);
9144
9145 /* return the new state to the caller */
9146 *aMachineState = mData->mMachineState;
9147
9148 return S_OK;
9149}
9150
9151/**
9152 * @note Locks mParent + other objects for writing!
9153 */
9154STDMETHODIMP SessionMachine::DiscardCurrentSnapshotAndState (
9155 IConsole *aInitiator, MachineState_T *aMachineState, IProgress **aProgress)
9156{
9157 LogFlowThisFunc (("\n"));
9158
9159 AssertReturn (aInitiator, E_INVALIDARG);
9160 AssertReturn (aMachineState && aProgress, E_POINTER);
9161
9162 AutoCaller autoCaller (this);
9163 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
9164
9165 /* Progress::init() needs mParent lock */
9166 AutoMultiWriteLock2 alock (mParent, this);
9167
9168 ComAssertRet (mData->mMachineState < MachineState_Running, E_FAIL);
9169
9170 if (mData->mCurrentSnapshot.isNull())
9171 return setError (E_FAIL,
9172 tr ("Could not discard the current state of the machine '%ls' "
9173 "because it doesn't have any snapshots"),
9174 mUserData->mName.raw());
9175
9176 /*
9177 * create a progress object. The number of operations is:
9178 * 1 (preparing) + # of VDIs in the current snapshot +
9179 * # of VDIs in the previous snapshot +
9180 * 1 (if we need to copy the saved state file of the previous snapshot)
9181 * or (if there is no previous snapshot):
9182 * 1 (preparing) + # of VDIs in the current snapshot * 2 +
9183 * 1 (if we need to copy the saved state file of the current snapshot)
9184 */
9185 ComObjPtr <Progress> progress;
9186 progress.createObject();
9187 {
9188 ComObjPtr <Snapshot> curSnapshot = mData->mCurrentSnapshot;
9189 ComObjPtr <Snapshot> prevSnapshot = mData->mCurrentSnapshot->parent();
9190
9191 ULONG opCount = 1;
9192 if (prevSnapshot)
9193 {
9194 opCount += curSnapshot->data().mMachine->mHDData->mHDAttachments.size();
9195 opCount += prevSnapshot->data().mMachine->mHDData->mHDAttachments.size();
9196 if (prevSnapshot->stateFilePath())
9197 ++ opCount;
9198 }
9199 else
9200 {
9201 opCount += curSnapshot->data().mMachine->mHDData->mHDAttachments.size() * 2;
9202 if (curSnapshot->stateFilePath())
9203 ++ opCount;
9204 }
9205
9206 progress->init (mParent, aInitiator,
9207 Bstr (tr ("Discarding current machine snapshot and state")),
9208 FALSE /* aCancelable */, opCount,
9209 Bstr (tr ("Preparing to discard current snapshot and state")));
9210 }
9211
9212 /* create and start the task on a separate thread */
9213 DiscardCurrentStateTask *task =
9214 new DiscardCurrentStateTask (this, progress, true /* discardCurSnapshot */);
9215 int vrc = RTThreadCreate (NULL, taskHandler,
9216 (void *) task,
9217 0, RTTHREADTYPE_MAIN_WORKER, 0, "DiscardCurState");
9218 if (VBOX_FAILURE (vrc))
9219 delete task;
9220 ComAssertRCRet (vrc, E_FAIL);
9221
9222 /* set the proper machine state (note: after creating a Task instance) */
9223 setMachineState (MachineState_Discarding);
9224
9225 /* return the progress to the caller */
9226 progress.queryInterfaceTo (aProgress);
9227
9228 /* return the new state to the caller */
9229 *aMachineState = mData->mMachineState;
9230
9231 return S_OK;
9232}
9233
9234STDMETHODIMP SessionMachine::PullGuestProperties (ComSafeArrayOut(BSTR, aNames), ComSafeArrayOut(BSTR, aValues),
9235 ComSafeArrayOut(ULONG64, aTimestamps), ComSafeArrayOut(BSTR, aFlags))
9236{
9237 LogFlowThisFunc (("\n"));
9238
9239#ifdef VBOX_WITH_GUEST_PROPS
9240 using namespace guestProp;
9241
9242 AutoCaller autoCaller (this);
9243 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
9244
9245 AutoReadLock alock (this);
9246
9247 AssertReturn(!ComSafeArrayOutIsNull (aNames), E_POINTER);
9248 AssertReturn(!ComSafeArrayOutIsNull (aValues), E_POINTER);
9249 AssertReturn(!ComSafeArrayOutIsNull (aTimestamps), E_POINTER);
9250 AssertReturn(!ComSafeArrayOutIsNull (aFlags), E_POINTER);
9251
9252 size_t cEntries = mHWData->mGuestProperties.size();
9253 com::SafeArray <BSTR> names(cEntries);
9254 com::SafeArray <BSTR> values(cEntries);
9255 com::SafeArray <ULONG64> timestamps(cEntries);
9256 com::SafeArray <BSTR> flags(cEntries);
9257 unsigned i = 0;
9258 for (HWData::GuestPropertyList::iterator it = mHWData->mGuestProperties.begin();
9259 it != mHWData->mGuestProperties.end(); ++it)
9260 {
9261 char szFlags[MAX_FLAGS_LEN + 1];
9262 it->mName.cloneTo(&names[i]);
9263 it->mValue.cloneTo(&values[i]);
9264 timestamps[i] = it->mTimestamp;
9265 writeFlags(it->mFlags, szFlags);
9266 Bstr(szFlags).cloneTo(&flags[i]);
9267 ++i;
9268 }
9269 names.detachTo(ComSafeArrayOutArg (aNames));
9270 values.detachTo(ComSafeArrayOutArg (aValues));
9271 timestamps.detachTo(ComSafeArrayOutArg (aTimestamps));
9272 flags.detachTo(ComSafeArrayOutArg (aFlags));
9273 mHWData->mPropertyServiceActive = true;
9274 return S_OK;
9275#else
9276 return E_NOTIMPL;
9277#endif
9278}
9279
9280STDMETHODIMP SessionMachine::PushGuestProperties (ComSafeArrayIn(INPTR BSTR, aNames),
9281 ComSafeArrayIn(INPTR BSTR, aValues),
9282 ComSafeArrayIn(ULONG64, aTimestamps),
9283 ComSafeArrayIn(INPTR BSTR, aFlags))
9284{
9285 LogFlowThisFunc (("\n"));
9286
9287#ifdef VBOX_WITH_GUEST_PROPS
9288 using namespace guestProp;
9289
9290 AutoCaller autoCaller (this);
9291 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
9292
9293 AutoWriteLock alock (this);
9294
9295 /* Temporarily reset the registered flag, so that our machine state
9296 * changes (i.e. mHWData.backup()) succeed. (isMutable() used in
9297 * all setters will return FALSE for a Machine instance if mRegistered
9298 * is TRUE). This is copied from registeredInit(), and may or may not be
9299 * the right way to handle this. */
9300 mData->mRegistered = FALSE;
9301 HRESULT rc = checkStateDependency (MutableStateDep);
9302 LogRel(("checkStateDependency (MutableStateDep) returned 0x%x\n", rc));
9303 CheckComRCReturnRC (rc);
9304
9305 // ComAssertRet (mData->mMachineState < MachineState_Running, E_FAIL);
9306
9307 AssertReturn(!ComSafeArrayInIsNull (aNames), E_POINTER);
9308 AssertReturn(!ComSafeArrayInIsNull (aValues), E_POINTER);
9309 AssertReturn(!ComSafeArrayInIsNull (aTimestamps), E_POINTER);
9310 AssertReturn(!ComSafeArrayInIsNull (aFlags), E_POINTER);
9311
9312 com::SafeArray <INPTR BSTR> names(ComSafeArrayInArg(aNames));
9313 com::SafeArray <INPTR BSTR> values(ComSafeArrayInArg(aValues));
9314 com::SafeArray <ULONG64> timestamps(ComSafeArrayInArg(aTimestamps));
9315 com::SafeArray <INPTR BSTR> flags(ComSafeArrayInArg(aFlags));
9316 DiscardSettings();
9317 mHWData.backup();
9318 mHWData->mGuestProperties.erase(mHWData->mGuestProperties.begin(),
9319 mHWData->mGuestProperties.end());
9320 for (unsigned i = 0; i < names.size(); ++i)
9321 {
9322 uint32_t fFlags = NILFLAG;
9323 validateFlags (Utf8Str(flags[i]).raw(), &fFlags);
9324 HWData::GuestProperty property = { names[i], values[i], timestamps[i], fFlags };
9325 mHWData->mGuestProperties.push_back(property);
9326 }
9327 mHWData->mPropertyServiceActive = false;
9328 alock.unlock();
9329 SaveSettings();
9330 /* Restore the mRegistered flag. */
9331 mData->mRegistered = TRUE;
9332 return S_OK;
9333#else
9334 return E_NOTIMPL;
9335#endif
9336}
9337
9338STDMETHODIMP SessionMachine::PushGuestProperty (INPTR BSTR aName, INPTR BSTR aValue,
9339 ULONG64 aTimestamp, INPTR BSTR aFlags)
9340{
9341 LogFlowThisFunc (("\n"));
9342
9343#ifdef VBOX_WITH_GUEST_PROPS
9344 using namespace guestProp;
9345
9346 if (!VALID_PTR(aName))
9347 return E_POINTER;
9348 if ((aValue != NULL) && (!VALID_PTR(aValue) || !VALID_PTR(aFlags)))
9349 return E_POINTER; /* aValue can be NULL to indicate deletion */
9350
9351 uint32_t fFlags = NILFLAG;
9352 if ((aFlags != NULL) && RT_FAILURE (validateFlags (Utf8Str(aFlags).raw(), &fFlags)))
9353 return E_INVALIDARG;
9354
9355 AutoCaller autoCaller (this);
9356 CheckComRCReturnRC (autoCaller.rc());
9357
9358 AutoWriteLock alock (this);
9359
9360 HRESULT rc = checkStateDependency (MutableStateDep);
9361 CheckComRCReturnRC (rc);
9362
9363 mHWData.backup();
9364 for (HWData::GuestPropertyList::iterator iter = mHWData->mGuestProperties.begin();
9365 iter != mHWData->mGuestProperties.end(); ++iter)
9366 if (aName == iter->mName)
9367 {
9368 mHWData->mGuestProperties.erase(iter);
9369 break;
9370 }
9371 if (aValue != NULL)
9372 {
9373 HWData::GuestProperty property = { aName, aValue, aTimestamp, fFlags };
9374 mHWData->mGuestProperties.push_back(property);
9375 }
9376
9377 /* send a callback notification if appropriate */
9378 alock.leave();
9379 if (matchesPatternComma (Utf8Str(mHWData->mGuestPropertyNotificationPatterns).raw(),
9380 Utf8Str(aName).raw()))
9381 mParent->onGuestPropertyChange (mData->mUuid, aName, aValue, aFlags);
9382
9383 return S_OK;
9384#else
9385 return E_NOTIMPL;
9386#endif
9387}
9388
9389// public methods only for internal purposes
9390/////////////////////////////////////////////////////////////////////////////
9391
9392/**
9393 * Called from the client watcher thread to check for expected or unexpected
9394 * death of the client process that has a direct session to this machine.
9395 *
9396 * On Win32 and on OS/2, this method is called only when we've got the
9397 * mutex (i.e. the client has either died or terminated normally) so it always
9398 * returns @c true (the client is terminated, the session machine is
9399 * uninitialized).
9400 *
9401 * On other platforms, the method returns @c true if the client process has
9402 * terminated normally or abnormally and the session machine was uninitialized,
9403 * and @c false if the client process is still alive.
9404 *
9405 * @note Locks this object for writing.
9406 */
9407bool SessionMachine::checkForDeath()
9408{
9409 Uninit::Reason reason;
9410 bool terminated = false;
9411
9412 /* Enclose autoCaller with a block because calling uninit() from under it
9413 * will deadlock. */
9414 {
9415 AutoCaller autoCaller (this);
9416 if (!autoCaller.isOk())
9417 {
9418 /* return true if not ready, to cause the client watcher to exclude
9419 * the corresponding session from watching */
9420 LogFlowThisFunc (("Already uninitialized!"));
9421 return true;
9422 }
9423
9424 AutoWriteLock alock (this);
9425
9426 /* Determine the reason of death: if the session state is Closing here,
9427 * everything is fine. Otherwise it means that the client did not call
9428 * OnSessionEnd() before it released the IPC semaphore. This may happen
9429 * either because the client process has abnormally terminated, or
9430 * because it simply forgot to call ISession::Close() before exiting. We
9431 * threat the latter also as an abnormal termination (see
9432 * Session::uninit() for details). */
9433 reason = mData->mSession.mState == SessionState_Closing ?
9434 Uninit::Normal :
9435 Uninit::Abnormal;
9436
9437#if defined(RT_OS_WINDOWS)
9438
9439 AssertMsg (mIPCSem, ("semaphore must be created"));
9440
9441 /* release the IPC mutex */
9442 ::ReleaseMutex (mIPCSem);
9443
9444 terminated = true;
9445
9446#elif defined(RT_OS_OS2)
9447
9448 AssertMsg (mIPCSem, ("semaphore must be created"));
9449
9450 /* release the IPC mutex */
9451 ::DosReleaseMutexSem (mIPCSem);
9452
9453 terminated = true;
9454
9455#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
9456
9457 AssertMsg (mIPCSem >= 0, ("semaphore must be created"));
9458
9459 int val = ::semctl (mIPCSem, 0, GETVAL);
9460 if (val > 0)
9461 {
9462 /* the semaphore is signaled, meaning the session is terminated */
9463 terminated = true;
9464 }
9465
9466#else
9467# error "Port me!"
9468#endif
9469
9470 } /* AutoCaller block */
9471
9472 if (terminated)
9473 uninit (reason);
9474
9475 return terminated;
9476}
9477
9478/**
9479 * @note Locks this object for reading.
9480 */
9481HRESULT SessionMachine::onDVDDriveChange()
9482{
9483 LogFlowThisFunc (("\n"));
9484
9485 AutoCaller autoCaller (this);
9486 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
9487
9488 ComPtr <IInternalSessionControl> directControl;
9489 {
9490 AutoReadLock alock (this);
9491 directControl = mData->mSession.mDirectControl;
9492 }
9493
9494 /* ignore notifications sent after #OnSessionEnd() is called */
9495 if (!directControl)
9496 return S_OK;
9497
9498 return directControl->OnDVDDriveChange();
9499}
9500
9501/**
9502 * @note Locks this object for reading.
9503 */
9504HRESULT SessionMachine::onFloppyDriveChange()
9505{
9506 LogFlowThisFunc (("\n"));
9507
9508 AutoCaller autoCaller (this);
9509 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
9510
9511 ComPtr <IInternalSessionControl> directControl;
9512 {
9513 AutoReadLock alock (this);
9514 directControl = mData->mSession.mDirectControl;
9515 }
9516
9517 /* ignore notifications sent after #OnSessionEnd() is called */
9518 if (!directControl)
9519 return S_OK;
9520
9521 return directControl->OnFloppyDriveChange();
9522}
9523
9524/**
9525 * @note Locks this object for reading.
9526 */
9527HRESULT SessionMachine::onNetworkAdapterChange(INetworkAdapter *networkAdapter)
9528{
9529 LogFlowThisFunc (("\n"));
9530
9531 AutoCaller autoCaller (this);
9532 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
9533
9534 ComPtr <IInternalSessionControl> directControl;
9535 {
9536 AutoReadLock alock (this);
9537 directControl = mData->mSession.mDirectControl;
9538 }
9539
9540 /* ignore notifications sent after #OnSessionEnd() is called */
9541 if (!directControl)
9542 return S_OK;
9543
9544 return directControl->OnNetworkAdapterChange(networkAdapter);
9545}
9546
9547/**
9548 * @note Locks this object for reading.
9549 */
9550HRESULT SessionMachine::onSerialPortChange(ISerialPort *serialPort)
9551{
9552 LogFlowThisFunc (("\n"));
9553
9554 AutoCaller autoCaller (this);
9555 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
9556
9557 ComPtr <IInternalSessionControl> directControl;
9558 {
9559 AutoReadLock alock (this);
9560 directControl = mData->mSession.mDirectControl;
9561 }
9562
9563 /* ignore notifications sent after #OnSessionEnd() is called */
9564 if (!directControl)
9565 return S_OK;
9566
9567 return directControl->OnSerialPortChange(serialPort);
9568}
9569
9570/**
9571 * @note Locks this object for reading.
9572 */
9573HRESULT SessionMachine::onParallelPortChange(IParallelPort *parallelPort)
9574{
9575 LogFlowThisFunc (("\n"));
9576
9577 AutoCaller autoCaller (this);
9578 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
9579
9580 ComPtr <IInternalSessionControl> directControl;
9581 {
9582 AutoReadLock alock (this);
9583 directControl = mData->mSession.mDirectControl;
9584 }
9585
9586 /* ignore notifications sent after #OnSessionEnd() is called */
9587 if (!directControl)
9588 return S_OK;
9589
9590 return directControl->OnParallelPortChange(parallelPort);
9591}
9592
9593/**
9594 * @note Locks this object for reading.
9595 */
9596HRESULT SessionMachine::onVRDPServerChange()
9597{
9598 LogFlowThisFunc (("\n"));
9599
9600 AutoCaller autoCaller (this);
9601 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
9602
9603 ComPtr <IInternalSessionControl> directControl;
9604 {
9605 AutoReadLock alock (this);
9606 directControl = mData->mSession.mDirectControl;
9607 }
9608
9609 /* ignore notifications sent after #OnSessionEnd() is called */
9610 if (!directControl)
9611 return S_OK;
9612
9613 return directControl->OnVRDPServerChange();
9614}
9615
9616/**
9617 * @note Locks this object for reading.
9618 */
9619HRESULT SessionMachine::onUSBControllerChange()
9620{
9621 LogFlowThisFunc (("\n"));
9622
9623 AutoCaller autoCaller (this);
9624 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
9625
9626 ComPtr <IInternalSessionControl> directControl;
9627 {
9628 AutoReadLock alock (this);
9629 directControl = mData->mSession.mDirectControl;
9630 }
9631
9632 /* ignore notifications sent after #OnSessionEnd() is called */
9633 if (!directControl)
9634 return S_OK;
9635
9636 return directControl->OnUSBControllerChange();
9637}
9638
9639/**
9640 * @note Locks this object for reading.
9641 */
9642HRESULT SessionMachine::onSharedFolderChange()
9643{
9644 LogFlowThisFunc (("\n"));
9645
9646 AutoCaller autoCaller (this);
9647 AssertComRCReturnRC (autoCaller.rc());
9648
9649 ComPtr <IInternalSessionControl> directControl;
9650 {
9651 AutoReadLock alock (this);
9652 directControl = mData->mSession.mDirectControl;
9653 }
9654
9655 /* ignore notifications sent after #OnSessionEnd() is called */
9656 if (!directControl)
9657 return S_OK;
9658
9659 return directControl->OnSharedFolderChange (FALSE /* aGlobal */);
9660}
9661
9662/**
9663 * Returns @c true if this machine's USB controller reports it has a matching
9664 * filter for the given USB device and @c false otherwise.
9665 *
9666 * @note Locks this object for reading.
9667 */
9668bool SessionMachine::hasMatchingUSBFilter (const ComObjPtr <HostUSBDevice> &aDevice, ULONG *aMaskedIfs)
9669{
9670 AutoCaller autoCaller (this);
9671 /* silently return if not ready -- this method may be called after the
9672 * direct machine session has been called */
9673 if (!autoCaller.isOk())
9674 return false;
9675
9676 AutoReadLock alock (this);
9677
9678#ifdef VBOX_WITH_USB
9679 switch (mData->mMachineState)
9680 {
9681 case MachineState_Starting:
9682 case MachineState_Restoring:
9683 case MachineState_Paused:
9684 case MachineState_Running:
9685 return mUSBController->hasMatchingFilter (aDevice, aMaskedIfs);
9686 default: break;
9687 }
9688#endif
9689 return false;
9690}
9691
9692/**
9693 * @note The calls shall hold no locks. Will temporarily lock this object for reading.
9694 */
9695HRESULT SessionMachine::onUSBDeviceAttach (IUSBDevice *aDevice,
9696 IVirtualBoxErrorInfo *aError,
9697 ULONG aMaskedIfs)
9698{
9699 LogFlowThisFunc (("\n"));
9700
9701 AutoCaller autoCaller (this);
9702
9703 /* This notification may happen after the machine object has been
9704 * uninitialized (the session was closed), so don't assert. */
9705 CheckComRCReturnRC (autoCaller.rc());
9706
9707 ComPtr <IInternalSessionControl> directControl;
9708 {
9709 AutoReadLock alock (this);
9710 directControl = mData->mSession.mDirectControl;
9711 }
9712
9713 /* fail on notifications sent after #OnSessionEnd() is called, it is
9714 * expected by the caller */
9715 if (!directControl)
9716 return E_FAIL;
9717
9718 /* No locks should be held at this point. */
9719 AssertMsg (RTThreadGetWriteLockCount (RTThreadSelf()) == 0, ("%d\n", RTThreadGetWriteLockCount (RTThreadSelf())));
9720 AssertMsg (RTThreadGetReadLockCount (RTThreadSelf()) == 0, ("%d\n", RTThreadGetReadLockCount (RTThreadSelf())));
9721
9722 return directControl->OnUSBDeviceAttach (aDevice, aError, aMaskedIfs);
9723}
9724
9725/**
9726 * @note The calls shall hold no locks. Will temporarily lock this object for reading.
9727 */
9728HRESULT SessionMachine::onUSBDeviceDetach (INPTR GUIDPARAM aId,
9729 IVirtualBoxErrorInfo *aError)
9730{
9731 LogFlowThisFunc (("\n"));
9732
9733 AutoCaller autoCaller (this);
9734
9735 /* This notification may happen after the machine object has been
9736 * uninitialized (the session was closed), so don't assert. */
9737 CheckComRCReturnRC (autoCaller.rc());
9738
9739 ComPtr <IInternalSessionControl> directControl;
9740 {
9741 AutoReadLock alock (this);
9742 directControl = mData->mSession.mDirectControl;
9743 }
9744
9745 /* fail on notifications sent after #OnSessionEnd() is called, it is
9746 * expected by the caller */
9747 if (!directControl)
9748 return E_FAIL;
9749
9750 /* No locks should be held at this point. */
9751 AssertMsg (RTThreadGetWriteLockCount (RTThreadSelf()) == 0, ("%d\n", RTThreadGetWriteLockCount (RTThreadSelf())));
9752 AssertMsg (RTThreadGetReadLockCount (RTThreadSelf()) == 0, ("%d\n", RTThreadGetReadLockCount (RTThreadSelf())));
9753
9754 return directControl->OnUSBDeviceDetach (aId, aError);
9755}
9756
9757// protected methods
9758/////////////////////////////////////////////////////////////////////////////
9759
9760/**
9761 * Helper method to finalize saving the state.
9762 *
9763 * @note Must be called from under this object's lock.
9764 *
9765 * @param aSuccess TRUE if the snapshot has been taken successfully
9766 *
9767 * @note Locks mParent + this objects for writing.
9768 */
9769HRESULT SessionMachine::endSavingState (BOOL aSuccess)
9770{
9771 LogFlowThisFuncEnter();
9772
9773 AutoCaller autoCaller (this);
9774 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
9775
9776 /* mParent->removeProgress() and saveSettings() need mParent lock */
9777 AutoMultiWriteLock2 alock (mParent, this);
9778
9779 HRESULT rc = S_OK;
9780
9781 if (aSuccess)
9782 {
9783 mSSData->mStateFilePath = mSnapshotData.mStateFilePath;
9784
9785 /* save all VM settings */
9786 rc = saveSettings();
9787 }
9788 else
9789 {
9790 /* delete the saved state file (it might have been already created) */
9791 RTFileDelete (Utf8Str (mSnapshotData.mStateFilePath));
9792 }
9793
9794 /* remove the completed progress object */
9795 mParent->removeProgress (mSnapshotData.mProgressId);
9796
9797 /* clear out the temporary saved state data */
9798 mSnapshotData.mLastState = MachineState_Null;
9799 mSnapshotData.mProgressId.clear();
9800 mSnapshotData.mStateFilePath.setNull();
9801
9802 LogFlowThisFuncLeave();
9803 return rc;
9804}
9805
9806/**
9807 * Helper method to finalize taking a snapshot.
9808 * Gets called only from #EndTakingSnapshot() that is expected to
9809 * be called by the VM process when it finishes *all* the tasks related to
9810 * taking a snapshot, either scucessfully or unsuccessfilly.
9811 *
9812 * @param aSuccess TRUE if the snapshot has been taken successfully
9813 *
9814 * @note Locks mParent + this objects for writing.
9815 */
9816HRESULT SessionMachine::endTakingSnapshot (BOOL aSuccess)
9817{
9818 LogFlowThisFuncEnter();
9819
9820 AutoCaller autoCaller (this);
9821 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
9822
9823 /* Progress object uninitialization needs mParent lock */
9824 AutoMultiWriteLock2 alock (mParent, this);
9825
9826 HRESULT rc = S_OK;
9827
9828 if (aSuccess)
9829 {
9830 /* the server progress must be completed on success */
9831 Assert (mSnapshotData.mServerProgress->completed());
9832
9833 mData->mCurrentSnapshot = mSnapshotData.mSnapshot;
9834 /* memorize the first snapshot if necessary */
9835 if (!mData->mFirstSnapshot)
9836 mData->mFirstSnapshot = mData->mCurrentSnapshot;
9837
9838 int opFlags = SaveSS_AddOp | SaveSS_UpdateCurrentId;
9839 if (mSnapshotData.mLastState != MachineState_Paused && !isModified())
9840 {
9841 /*
9842 * the machine was powered off or saved when taking a snapshot,
9843 * so reset the mCurrentStateModified flag
9844 */
9845 mData->mCurrentStateModified = FALSE;
9846 opFlags |= SaveSS_UpdateCurStateModified;
9847 }
9848
9849 rc = saveSnapshotSettings (mSnapshotData.mSnapshot, opFlags);
9850 }
9851
9852 if (!aSuccess || FAILED (rc))
9853 {
9854 if (mSnapshotData.mSnapshot)
9855 {
9856 /* wait for the completion of the server progress (diff VDI creation) */
9857 /// @todo (dmik) later, we will definitely want to cancel it instead
9858 // (when the cancel function is implemented)
9859 mSnapshotData.mServerProgress->WaitForCompletion (-1);
9860
9861 /*
9862 * delete all differencing VDIs created
9863 * (this will attach their parents back)
9864 */
9865 rc = deleteSnapshotDiffs (mSnapshotData.mSnapshot);
9866 /* continue cleanup on error */
9867
9868 /* delete the saved state file (it might have been already created) */
9869 if (mSnapshotData.mSnapshot->stateFilePath())
9870 RTFileDelete (Utf8Str (mSnapshotData.mSnapshot->stateFilePath()));
9871
9872 mSnapshotData.mSnapshot->uninit();
9873 }
9874 }
9875
9876 /* inform callbacks */
9877 if (aSuccess && SUCCEEDED (rc))
9878 mParent->onSnapshotTaken (mData->mUuid, mSnapshotData.mSnapshot->data().mId);
9879
9880 /* clear out the snapshot data */
9881 mSnapshotData.mLastState = MachineState_Null;
9882 mSnapshotData.mSnapshot.setNull();
9883 mSnapshotData.mServerProgress.setNull();
9884 /* uninitialize the combined progress (to remove it from the VBox collection) */
9885 if (!mSnapshotData.mCombinedProgress.isNull())
9886 {
9887 mSnapshotData.mCombinedProgress->uninit();
9888 mSnapshotData.mCombinedProgress.setNull();
9889 }
9890
9891 LogFlowThisFuncLeave();
9892 return rc;
9893}
9894
9895/**
9896 * Take snapshot task handler.
9897 * Must be called only by TakeSnapshotTask::handler()!
9898 *
9899 * The sole purpose of this task is to asynchronously create differencing VDIs
9900 * and copy the saved state file (when necessary). The VM process will wait
9901 * for this task to complete using the mSnapshotData.mServerProgress
9902 * returned to it.
9903 *
9904 * @note Locks mParent + this objects for writing.
9905 */
9906void SessionMachine::takeSnapshotHandler (TakeSnapshotTask &aTask)
9907{
9908 LogFlowThisFuncEnter();
9909
9910 AutoCaller autoCaller (this);
9911
9912 LogFlowThisFunc (("state=%d\n", autoCaller.state()));
9913 if (!autoCaller.isOk())
9914 {
9915 /*
9916 * we might have been uninitialized because the session was
9917 * accidentally closed by the client, so don't assert
9918 */
9919 LogFlowThisFuncLeave();
9920 return;
9921 }
9922
9923 /* endTakingSnapshot() needs mParent lock */
9924 AutoMultiWriteLock2 alock (mParent, this);
9925
9926 HRESULT rc = S_OK;
9927
9928 LogFlowThisFunc (("Creating differencing VDIs...\n"));
9929
9930 /* create new differencing hard disks and attach them to this machine */
9931 rc = createSnapshotDiffs (&mSnapshotData.mSnapshot->data().mId,
9932 mUserData->mSnapshotFolderFull,
9933 mSnapshotData.mServerProgress,
9934 true /* aOnline */);
9935
9936 if (SUCCEEDED (rc) && mSnapshotData.mLastState == MachineState_Saved)
9937 {
9938 Utf8Str stateFrom = mSSData->mStateFilePath;
9939 Utf8Str stateTo = mSnapshotData.mSnapshot->stateFilePath();
9940
9941 LogFlowThisFunc (("Copying the execution state from '%s' to '%s'...\n",
9942 stateFrom.raw(), stateTo.raw()));
9943
9944 mSnapshotData.mServerProgress->advanceOperation (
9945 Bstr (tr ("Copying the execution state")));
9946
9947 /*
9948 * We can safely leave the lock here:
9949 * mMachineState is MachineState_Saving here
9950 */
9951 alock.leave();
9952
9953 /* copy the state file */
9954 int vrc = RTFileCopyEx (stateFrom, stateTo, 0, progressCallback,
9955 static_cast <Progress *> (mSnapshotData.mServerProgress));
9956
9957 alock.enter();
9958
9959 if (VBOX_FAILURE (vrc))
9960 rc = setError (E_FAIL,
9961 tr ("Could not copy the state file '%ls' to '%ls' (%Vrc)"),
9962 stateFrom.raw(), stateTo.raw());
9963 }
9964
9965 /*
9966 * we have to call endTakingSnapshot() here if the snapshot was taken
9967 * offline, because the VM process will not do it in this case
9968 */
9969 if (mSnapshotData.mLastState != MachineState_Paused)
9970 {
9971 LogFlowThisFunc (("Finalizing the taken snapshot (rc=%08X)...\n", rc));
9972
9973 setMachineState (mSnapshotData.mLastState);
9974 updateMachineStateOnClient();
9975
9976 /* finalize the progress after setting the state, for consistency */
9977 mSnapshotData.mServerProgress->notifyComplete (rc);
9978
9979 endTakingSnapshot (SUCCEEDED (rc));
9980 }
9981 else
9982 {
9983 mSnapshotData.mServerProgress->notifyComplete (rc);
9984 }
9985
9986 LogFlowThisFuncLeave();
9987}
9988
9989/**
9990 * Discard snapshot task handler.
9991 * Must be called only by DiscardSnapshotTask::handler()!
9992 *
9993 * When aTask.subTask is true, the associated progress object is left
9994 * uncompleted on success. On failure, the progress is marked as completed
9995 * regardless of this parameter.
9996 *
9997 * @note Locks mParent + this + child objects for writing!
9998 */
9999void SessionMachine::discardSnapshotHandler (DiscardSnapshotTask &aTask)
10000{
10001 LogFlowThisFuncEnter();
10002
10003 AutoCaller autoCaller (this);
10004
10005 LogFlowThisFunc (("state=%d\n", autoCaller.state()));
10006 if (!autoCaller.isOk())
10007 {
10008 /*
10009 * we might have been uninitialized because the session was
10010 * accidentally closed by the client, so don't assert
10011 */
10012 aTask.progress->notifyComplete (
10013 E_FAIL, COM_IIDOF (IMachine), getComponentName(),
10014 tr ("The session has been accidentally closed"));
10015
10016 LogFlowThisFuncLeave();
10017 return;
10018 }
10019
10020 /* Progress::notifyComplete() et al., saveSettings() need mParent lock.
10021 * Also safely lock the snapshot stuff in the direction parent->child */
10022 AutoMultiWriteLock4 alock (mParent->lockHandle(), this->lockHandle(),
10023 aTask.snapshot->lockHandle(),
10024 aTask.snapshot->childrenLock());
10025
10026 ComObjPtr <SnapshotMachine> sm = aTask.snapshot->data().mMachine;
10027 /* no need to lock the snapshot machine since it is const by definiton */
10028
10029 HRESULT rc = S_OK;
10030
10031 /* save the snapshot ID (for callbacks) */
10032 Guid snapshotId = aTask.snapshot->data().mId;
10033
10034 do
10035 {
10036 /* first pass: */
10037 LogFlowThisFunc (("Check hard disk accessibility and affected machines...\n"));
10038
10039 HDData::HDAttachmentList::const_iterator it;
10040 for (it = sm->mHDData->mHDAttachments.begin();
10041 it != sm->mHDData->mHDAttachments.end();
10042 ++ it)
10043 {
10044 ComObjPtr <HardDiskAttachment> hda = *it;
10045 ComObjPtr <HardDisk> hd = hda->hardDisk();
10046 ComObjPtr <HardDisk> parent = hd->parent();
10047
10048 AutoWriteLock hdLock (hd);
10049
10050 if (hd->hasForeignChildren())
10051 {
10052 rc = setError (E_FAIL,
10053 tr ("One or more hard disks belonging to other machines are "
10054 "based on the hard disk '%ls' stored in the snapshot '%ls'"),
10055 hd->toString().raw(), aTask.snapshot->data().mName.raw());
10056 break;
10057 }
10058
10059 if (hd->type() == HardDiskType_Normal)
10060 {
10061 AutoWriteLock hdChildrenLock (hd->childrenLock ());
10062 size_t childrenCount = hd->children().size();
10063 if (childrenCount > 1)
10064 {
10065 rc = setError (E_FAIL,
10066 tr ("Normal hard disk '%ls' stored in the snapshot '%ls' "
10067 "has more than one child hard disk (%d)"),
10068 hd->toString().raw(), aTask.snapshot->data().mName.raw(),
10069 childrenCount);
10070 break;
10071 }
10072 }
10073 else
10074 {
10075 ComAssertMsgFailedBreak (("Invalid hard disk type %d\n", hd->type()),
10076 rc = E_FAIL);
10077 }
10078
10079 Bstr accessError;
10080 rc = hd->getAccessibleWithChildren (accessError);
10081 CheckComRCBreakRC (rc);
10082
10083 if (!accessError.isNull())
10084 {
10085 rc = setError (E_FAIL,
10086 tr ("Hard disk '%ls' stored in the snapshot '%ls' is not "
10087 "accessible (%ls)"),
10088 hd->toString().raw(), aTask.snapshot->data().mName.raw(),
10089 accessError.raw());
10090 break;
10091 }
10092
10093 rc = hd->setBusyWithChildren();
10094 if (FAILED (rc))
10095 {
10096 /* reset the busy flag of all previous hard disks */
10097 while (it != sm->mHDData->mHDAttachments.begin())
10098 (*(-- it))->hardDisk()->clearBusyWithChildren();
10099 break;
10100 }
10101 }
10102
10103 CheckComRCBreakRC (rc);
10104
10105 /* second pass: */
10106 LogFlowThisFunc (("Performing actual vdi merging...\n"));
10107
10108 for (it = sm->mHDData->mHDAttachments.begin();
10109 it != sm->mHDData->mHDAttachments.end();
10110 ++ it)
10111 {
10112 ComObjPtr <HardDiskAttachment> hda = *it;
10113 ComObjPtr <HardDisk> hd = hda->hardDisk();
10114 ComObjPtr <HardDisk> parent = hd->parent();
10115
10116 AutoWriteLock hdLock (hd);
10117
10118 Bstr hdRootString = hd->root()->toString (true /* aShort */);
10119
10120 if (parent)
10121 {
10122 if (hd->isParentImmutable())
10123 {
10124 aTask.progress->advanceOperation (Bstr (Utf8StrFmt (
10125 tr ("Discarding changes to immutable hard disk '%ls'"),
10126 hdRootString.raw())));
10127
10128 /* clear the busy flag before unregistering */
10129 hd->clearBusy();
10130
10131 /*
10132 * unregisterDiffHardDisk() is supposed to delete and uninit
10133 * the differencing hard disk
10134 */
10135 rc = mParent->unregisterDiffHardDisk (hd);
10136 CheckComRCBreakRC (rc);
10137 continue;
10138 }
10139 else
10140 {
10141 /*
10142 * differencing VDI:
10143 * merge this image to all its children
10144 */
10145
10146 aTask.progress->advanceOperation (Bstr (Utf8StrFmt (
10147 tr ("Merging changes to normal hard disk '%ls' to children"),
10148 hdRootString.raw())));
10149
10150 alock.leave();
10151
10152 rc = hd->asVDI()->mergeImageToChildren (aTask.progress);
10153
10154 alock.enter();
10155
10156 // debug code
10157 // if (it != sm->mHDData->mHDAttachments.begin())
10158 // {
10159 // rc = setError (E_FAIL, "Simulated failure");
10160 // break;
10161 //}
10162
10163 if (SUCCEEDED (rc))
10164 rc = mParent->unregisterDiffHardDisk (hd);
10165 else
10166 hd->clearBusyWithChildren();
10167
10168 CheckComRCBreakRC (rc);
10169 }
10170 }
10171 else if (hd->type() == HardDiskType_Normal)
10172 {
10173 /*
10174 * normal vdi has the only child or none
10175 * (checked in the first pass)
10176 */
10177
10178 ComObjPtr <HardDisk> child;
10179 {
10180 AutoWriteLock hdChildrenLock (hd->childrenLock ());
10181 if (hd->children().size())
10182 child = hd->children().front();
10183 }
10184
10185 if (child.isNull())
10186 {
10187 aTask.progress->advanceOperation (Bstr (Utf8StrFmt (
10188 tr ("Detaching normal hard disk '%ls'"),
10189 hdRootString.raw())));
10190
10191 /* just deassociate the normal image from this machine */
10192 hd->setMachineId (Guid());
10193 hd->setSnapshotId (Guid());
10194
10195 /* clear the busy flag */
10196 hd->clearBusy();
10197 }
10198 else
10199 {
10200 AutoWriteLock childLock (child);
10201
10202 aTask.progress->advanceOperation (Bstr (Utf8StrFmt (
10203 tr ("Preserving changes to normal hard disk '%ls'"),
10204 hdRootString.raw())));
10205
10206 ComObjPtr <Machine> cm;
10207 ComObjPtr <Snapshot> cs;
10208 ComObjPtr <HardDiskAttachment> childHda;
10209 rc = findHardDiskAttachment (child, &cm, &cs, &childHda);
10210 CheckComRCBreakRC (rc);
10211 /* must be the same machine (checked in the first pass) */
10212 ComAssertBreak (cm->mData->mUuid == mData->mUuid, rc = E_FAIL);
10213
10214 /* merge the child to this basic image */
10215
10216 alock.leave();
10217
10218 rc = child->asVDI()->mergeImageToParent (aTask.progress);
10219
10220 alock.enter();
10221
10222 if (SUCCEEDED (rc))
10223 rc = mParent->unregisterDiffHardDisk (child);
10224 else
10225 hd->clearBusyWithChildren();
10226
10227 CheckComRCBreakRC (rc);
10228
10229 /* reset the snapshot Id */
10230 hd->setSnapshotId (Guid());
10231
10232 /* replace the child image in the appropriate place */
10233 childHda->updateHardDisk (hd, FALSE /* aDirty */);
10234
10235 if (!cs)
10236 {
10237 aTask.settingsChanged = true;
10238 }
10239 else
10240 {
10241 rc = cm->saveSnapshotSettings (cs, SaveSS_UpdateAllOp);
10242 CheckComRCBreakRC (rc);
10243 }
10244 }
10245 }
10246 else
10247 {
10248 ComAssertMsgFailedBreak (("Invalid hard disk type %d\n", hd->type()),
10249 rc = E_FAIL);
10250 }
10251 }
10252
10253 /* preserve existing error info */
10254 ErrorInfoKeeper mergeEik;
10255 HRESULT mergeRc = rc;
10256
10257 if (FAILED (rc))
10258 {
10259 /* clear the busy flag on the rest of hard disks */
10260 for (++ it; it != sm->mHDData->mHDAttachments.end(); ++ it)
10261 (*it)->hardDisk()->clearBusyWithChildren();
10262 }
10263
10264 /*
10265 * we have to try to discard the snapshot even if merging failed
10266 * because some images might have been already merged (and deleted)
10267 */
10268
10269 do
10270 {
10271 LogFlowThisFunc (("Discarding the snapshot (reparenting children)...\n"));
10272
10273 /* It is important to uninitialize and delete all snapshot's hard
10274 * disk attachments as they are no longer valid -- otherwise the
10275 * code in Machine::uninitDataAndChildObjects() will mistakenly
10276 * perform hard disk deassociation. */
10277 for (HDData::HDAttachmentList::iterator it = sm->mHDData->mHDAttachments.begin();
10278 it != sm->mHDData->mHDAttachments.end();)
10279 {
10280 (*it)->uninit();
10281 it = sm->mHDData->mHDAttachments.erase (it);
10282 }
10283
10284 ComObjPtr <Snapshot> parentSnapshot = aTask.snapshot->parent();
10285
10286 /// @todo (dmik):
10287 // when we introduce clones later, discarding the snapshot
10288 // will affect the current and first snapshots of clones, if they are
10289 // direct children of this snapshot. So we will need to lock machines
10290 // associated with child snapshots as well and update mCurrentSnapshot
10291 // and/or mFirstSnapshot fields.
10292
10293 if (aTask.snapshot == mData->mCurrentSnapshot)
10294 {
10295 /* currently, the parent snapshot must refer to the same machine */
10296 ComAssertBreak (
10297 !parentSnapshot ||
10298 parentSnapshot->data().mMachine->mData->mUuid == mData->mUuid,
10299 rc = E_FAIL);
10300 mData->mCurrentSnapshot = parentSnapshot;
10301 /* mark the current state as modified */
10302 mData->mCurrentStateModified = TRUE;
10303 }
10304
10305 if (aTask.snapshot == mData->mFirstSnapshot)
10306 {
10307 /*
10308 * the first snapshot must have only one child when discarded,
10309 * or no children at all
10310 */
10311 ComAssertBreak (aTask.snapshot->children().size() <= 1, rc = E_FAIL);
10312
10313 if (aTask.snapshot->children().size() == 1)
10314 {
10315 ComObjPtr <Snapshot> childSnapshot = aTask.snapshot->children().front();
10316 ComAssertBreak (
10317 childSnapshot->data().mMachine->mData->mUuid == mData->mUuid,
10318 rc = E_FAIL);
10319 mData->mFirstSnapshot = childSnapshot;
10320 }
10321 else
10322 mData->mFirstSnapshot.setNull();
10323 }
10324
10325 /// @todo (dmik)
10326 // if we implement some warning mechanism later, we'll have
10327 // to return a warning if the state file path cannot be deleted
10328 Bstr stateFilePath = aTask.snapshot->stateFilePath();
10329 if (stateFilePath)
10330 RTFileDelete (Utf8Str (stateFilePath));
10331
10332 aTask.snapshot->discard();
10333
10334 rc = saveSnapshotSettings (parentSnapshot,
10335 SaveSS_UpdateAllOp | SaveSS_UpdateCurrentId);
10336 }
10337 while (0);
10338
10339 /* restore the merge error if any (ErrorInfo will be restored
10340 * automatically) */
10341 if (FAILED (mergeRc))
10342 rc = mergeRc;
10343 }
10344 while (0);
10345
10346 if (!aTask.subTask || FAILED (rc))
10347 {
10348 if (!aTask.subTask)
10349 {
10350 /* preserve existing error info */
10351 ErrorInfoKeeper eik;
10352
10353 /* restore the machine state */
10354 setMachineState (aTask.state);
10355 updateMachineStateOnClient();
10356
10357 /*
10358 * save settings anyway, since we've already changed the current
10359 * machine configuration
10360 */
10361 if (aTask.settingsChanged)
10362 {
10363 saveSettings (true /* aMarkCurStateAsModified */,
10364 true /* aInformCallbacksAnyway */);
10365 }
10366 }
10367
10368 /* set the result (this will try to fetch current error info on failure) */
10369 aTask.progress->notifyComplete (rc);
10370 }
10371
10372 if (SUCCEEDED (rc))
10373 mParent->onSnapshotDiscarded (mData->mUuid, snapshotId);
10374
10375 LogFlowThisFunc (("Done discarding snapshot (rc=%08X)\n", rc));
10376 LogFlowThisFuncLeave();
10377}
10378
10379/**
10380 * Discard current state task handler.
10381 * Must be called only by DiscardCurrentStateTask::handler()!
10382 *
10383 * @note Locks mParent + this object for writing.
10384 */
10385void SessionMachine::discardCurrentStateHandler (DiscardCurrentStateTask &aTask)
10386{
10387 LogFlowThisFuncEnter();
10388
10389 AutoCaller autoCaller (this);
10390
10391 LogFlowThisFunc (("state=%d\n", autoCaller.state()));
10392 if (!autoCaller.isOk())
10393 {
10394 /*
10395 * we might have been uninitialized because the session was
10396 * accidentally closed by the client, so don't assert
10397 */
10398 aTask.progress->notifyComplete (
10399 E_FAIL, COM_IIDOF (IMachine), getComponentName(),
10400 tr ("The session has been accidentally closed"));
10401
10402 LogFlowThisFuncLeave();
10403 return;
10404 }
10405
10406 /* Progress::notifyComplete() et al., saveSettings() need mParent lock */
10407 AutoMultiWriteLock2 alock (mParent, this);
10408
10409 /*
10410 * discard all current changes to mUserData (name, OSType etc.)
10411 * (note that the machine is powered off, so there is no need
10412 * to inform the direct session)
10413 */
10414 if (isModified())
10415 rollback (false /* aNotify */);
10416
10417 HRESULT rc = S_OK;
10418
10419 bool errorInSubtask = false;
10420 bool stateRestored = false;
10421
10422 const bool isLastSnapshot = mData->mCurrentSnapshot->parent().isNull();
10423
10424 do
10425 {
10426 /*
10427 * discard the saved state file if the machine was Saved prior
10428 * to this operation
10429 */
10430 if (aTask.state == MachineState_Saved)
10431 {
10432 Assert (!mSSData->mStateFilePath.isEmpty());
10433 RTFileDelete (Utf8Str (mSSData->mStateFilePath));
10434 mSSData->mStateFilePath.setNull();
10435 aTask.modifyLastState (MachineState_PoweredOff);
10436 rc = saveStateSettings (SaveSTS_StateFilePath);
10437 CheckComRCBreakRC (rc);
10438 }
10439
10440 if (aTask.discardCurrentSnapshot && !isLastSnapshot)
10441 {
10442 /*
10443 * the "discard current snapshot and state" task is in action,
10444 * the current snapshot is not the last one.
10445 * Discard the current snapshot first.
10446 */
10447
10448 DiscardSnapshotTask subTask (aTask, mData->mCurrentSnapshot);
10449 subTask.subTask = true;
10450 discardSnapshotHandler (subTask);
10451 aTask.settingsChanged = subTask.settingsChanged;
10452 if (aTask.progress->completed())
10453 {
10454 /*
10455 * the progress can be completed by a subtask only if there was
10456 * a failure
10457 */
10458 Assert (FAILED (aTask.progress->resultCode()));
10459 errorInSubtask = true;
10460 rc = aTask.progress->resultCode();
10461 break;
10462 }
10463 }
10464
10465 RTTIMESPEC snapshotTimeStamp;
10466 RTTimeSpecSetMilli (&snapshotTimeStamp, 0);
10467
10468 {
10469 ComObjPtr <Snapshot> curSnapshot = mData->mCurrentSnapshot;
10470 AutoWriteLock snapshotLock (curSnapshot);
10471
10472 /* remember the timestamp of the snapshot we're restoring from */
10473 snapshotTimeStamp = curSnapshot->data().mTimeStamp;
10474
10475 /* copy all hardware data from the current snapshot */
10476 copyFrom (curSnapshot->data().mMachine);
10477
10478 LogFlowThisFunc (("Restoring VDIs from the snapshot...\n"));
10479
10480 /* restore the attachmends from the snapshot */
10481 mHDData.backup();
10482 mHDData->mHDAttachments =
10483 curSnapshot->data().mMachine->mHDData->mHDAttachments;
10484
10485 snapshotLock.leave();
10486 alock.leave();
10487 rc = createSnapshotDiffs (NULL, mUserData->mSnapshotFolderFull,
10488 aTask.progress,
10489 false /* aOnline */);
10490 alock.enter();
10491 snapshotLock.enter();
10492
10493 if (FAILED (rc))
10494 {
10495 /* here we can still safely rollback, so do it */
10496 /* preserve existing error info */
10497 ErrorInfoKeeper eik;
10498 /* undo all changes */
10499 rollback (false /* aNotify */);
10500 break;
10501 }
10502
10503 /*
10504 * note: old VDIs will be deassociated/deleted on #commit() called
10505 * either from #saveSettings() or directly at the end
10506 */
10507
10508 /* should not have a saved state file associated at this point */
10509 Assert (mSSData->mStateFilePath.isNull());
10510
10511 if (curSnapshot->stateFilePath())
10512 {
10513 Utf8Str snapStateFilePath = curSnapshot->stateFilePath();
10514
10515 Utf8Str stateFilePath = Utf8StrFmt ("%ls%c{%Vuuid}.sav",
10516 mUserData->mSnapshotFolderFull.raw(),
10517 RTPATH_DELIMITER, mData->mUuid.raw());
10518
10519 LogFlowThisFunc (("Copying saved state file from '%s' to '%s'...\n",
10520 snapStateFilePath.raw(), stateFilePath.raw()));
10521
10522 aTask.progress->advanceOperation (
10523 Bstr (tr ("Restoring the execution state")));
10524
10525 /* copy the state file */
10526 snapshotLock.leave();
10527 alock.leave();
10528 int vrc = RTFileCopyEx (snapStateFilePath, stateFilePath,
10529 0, progressCallback, aTask.progress);
10530 alock.enter();
10531 snapshotLock.enter();
10532
10533 if (VBOX_SUCCESS (vrc))
10534 {
10535 mSSData->mStateFilePath = stateFilePath;
10536 }
10537 else
10538 {
10539 rc = setError (E_FAIL,
10540 tr ("Could not copy the state file '%s' to '%s' (%Vrc)"),
10541 snapStateFilePath.raw(), stateFilePath.raw(), vrc);
10542 break;
10543 }
10544 }
10545 }
10546
10547 bool informCallbacks = false;
10548
10549 if (aTask.discardCurrentSnapshot && isLastSnapshot)
10550 {
10551 /*
10552 * discard the current snapshot and state task is in action,
10553 * the current snapshot is the last one.
10554 * Discard the current snapshot after discarding the current state.
10555 */
10556
10557 /* commit changes to fixup hard disks before discarding */
10558 rc = commit();
10559 if (SUCCEEDED (rc))
10560 {
10561 DiscardSnapshotTask subTask (aTask, mData->mCurrentSnapshot);
10562 subTask.subTask = true;
10563 discardSnapshotHandler (subTask);
10564 aTask.settingsChanged = subTask.settingsChanged;
10565 if (aTask.progress->completed())
10566 {
10567 /*
10568 * the progress can be completed by a subtask only if there
10569 * was a failure
10570 */
10571 Assert (FAILED (aTask.progress->resultCode()));
10572 errorInSubtask = true;
10573 rc = aTask.progress->resultCode();
10574 }
10575 }
10576
10577 /*
10578 * we've committed already, so inform callbacks anyway to ensure
10579 * they don't miss some change
10580 */
10581 informCallbacks = true;
10582 }
10583
10584 /*
10585 * we have already discarded the current state, so set the
10586 * execution state accordingly no matter of the discard snapshot result
10587 */
10588 if (mSSData->mStateFilePath)
10589 setMachineState (MachineState_Saved);
10590 else
10591 setMachineState (MachineState_PoweredOff);
10592
10593 updateMachineStateOnClient();
10594 stateRestored = true;
10595
10596 if (errorInSubtask)
10597 break;
10598
10599 /* assign the timestamp from the snapshot */
10600 Assert (RTTimeSpecGetMilli (&snapshotTimeStamp) != 0);
10601 mData->mLastStateChange = snapshotTimeStamp;
10602
10603 /* mark the current state as not modified */
10604 mData->mCurrentStateModified = FALSE;
10605
10606 /* save all settings and commit */
10607 rc = saveSettings (false /* aMarkCurStateAsModified */,
10608 informCallbacks);
10609 aTask.settingsChanged = false;
10610 }
10611 while (0);
10612
10613 if (FAILED (rc))
10614 {
10615 /* preserve existing error info */
10616 ErrorInfoKeeper eik;
10617
10618 if (!stateRestored)
10619 {
10620 /* restore the machine state */
10621 setMachineState (aTask.state);
10622 updateMachineStateOnClient();
10623 }
10624
10625 /*
10626 * save all settings and commit if still modified (there is no way to
10627 * rollback properly). Note that isModified() will return true after
10628 * copyFrom(). Also save the settings if requested by the subtask.
10629 */
10630 if (isModified() || aTask.settingsChanged)
10631 {
10632 if (aTask.settingsChanged)
10633 saveSettings (true /* aMarkCurStateAsModified */,
10634 true /* aInformCallbacksAnyway */);
10635 else
10636 saveSettings();
10637 }
10638 }
10639
10640 if (!errorInSubtask)
10641 {
10642 /* set the result (this will try to fetch current error info on failure) */
10643 aTask.progress->notifyComplete (rc);
10644 }
10645
10646 if (SUCCEEDED (rc))
10647 mParent->onSnapshotDiscarded (mData->mUuid, Guid());
10648
10649 LogFlowThisFunc (("Done discarding current state (rc=%08X)\n", rc));
10650
10651 LogFlowThisFuncLeave();
10652}
10653
10654/**
10655 * Helper to change the machine state (reimplementation).
10656 *
10657 * @note Locks this object for writing.
10658 */
10659HRESULT SessionMachine::setMachineState (MachineState_T aMachineState)
10660{
10661 LogFlowThisFuncEnter();
10662 LogFlowThisFunc (("aMachineState=%d\n", aMachineState));
10663
10664 AutoCaller autoCaller (this);
10665 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
10666
10667 AutoWriteLock alock (this);
10668
10669 MachineState_T oldMachineState = mData->mMachineState;
10670
10671 AssertMsgReturn (oldMachineState != aMachineState,
10672 ("oldMachineState=%d, aMachineState=%d\n",
10673 oldMachineState, aMachineState), E_FAIL);
10674
10675 HRESULT rc = S_OK;
10676
10677 int stsFlags = 0;
10678 bool deleteSavedState = false;
10679
10680 /* detect some state transitions */
10681
10682 if (oldMachineState < MachineState_Running &&
10683 aMachineState >= MachineState_Running &&
10684 aMachineState != MachineState_Discarding)
10685 {
10686 /*
10687 * the EMT thread is about to start, so mark attached HDDs as busy
10688 * and all its ancestors as being in use
10689 */
10690 for (HDData::HDAttachmentList::const_iterator it =
10691 mHDData->mHDAttachments.begin();
10692 it != mHDData->mHDAttachments.end();
10693 ++ it)
10694 {
10695 ComObjPtr <HardDisk> hd = (*it)->hardDisk();
10696 AutoWriteLock hdLock (hd);
10697 hd->setBusy();
10698 hd->addReaderOnAncestors();
10699 }
10700 }
10701 else
10702 if (oldMachineState >= MachineState_Running &&
10703 oldMachineState != MachineState_Discarding &&
10704 aMachineState < MachineState_Running)
10705 {
10706 /*
10707 * the EMT thread stopped, so mark attached HDDs as no more busy
10708 * and remove the in-use flag from all its ancestors
10709 */
10710 for (HDData::HDAttachmentList::const_iterator it =
10711 mHDData->mHDAttachments.begin();
10712 it != mHDData->mHDAttachments.end();
10713 ++ it)
10714 {
10715 ComObjPtr <HardDisk> hd = (*it)->hardDisk();
10716 AutoWriteLock hdLock (hd);
10717 hd->releaseReaderOnAncestors();
10718 hd->clearBusy();
10719 }
10720 }
10721
10722 if (oldMachineState == MachineState_Restoring)
10723 {
10724 if (aMachineState != MachineState_Saved)
10725 {
10726 /*
10727 * delete the saved state file once the machine has finished
10728 * restoring from it (note that Console sets the state from
10729 * Restoring to Saved if the VM couldn't restore successfully,
10730 * to give the user an ability to fix an error and retry --
10731 * we keep the saved state file in this case)
10732 */
10733 deleteSavedState = true;
10734 }
10735 }
10736 else
10737 if (oldMachineState == MachineState_Saved &&
10738 (aMachineState == MachineState_PoweredOff ||
10739 aMachineState == MachineState_Aborted))
10740 {
10741 /*
10742 * delete the saved state after Console::DiscardSavedState() is called
10743 * or if the VM process (owning a direct VM session) crashed while the
10744 * VM was Saved
10745 */
10746
10747 /// @todo (dmik)
10748 // Not sure that deleting the saved state file just because of the
10749 // client death before it attempted to restore the VM is a good
10750 // thing. But when it crashes we need to go to the Aborted state
10751 // which cannot have the saved state file associated... The only
10752 // way to fix this is to make the Aborted condition not a VM state
10753 // but a bool flag: i.e., when a crash occurs, set it to true and
10754 // change the state to PoweredOff or Saved depending on the
10755 // saved state presence.
10756
10757 deleteSavedState = true;
10758 mData->mCurrentStateModified = TRUE;
10759 stsFlags |= SaveSTS_CurStateModified;
10760 }
10761
10762 if (aMachineState == MachineState_Starting ||
10763 aMachineState == MachineState_Restoring)
10764 {
10765 /*
10766 * set the current state modified flag to indicate that the
10767 * current state is no more identical to the state in the
10768 * current snapshot
10769 */
10770 if (!mData->mCurrentSnapshot.isNull())
10771 {
10772 mData->mCurrentStateModified = TRUE;
10773 stsFlags |= SaveSTS_CurStateModified;
10774 }
10775 }
10776
10777 if (deleteSavedState == true)
10778 {
10779 Assert (!mSSData->mStateFilePath.isEmpty());
10780 RTFileDelete (Utf8Str (mSSData->mStateFilePath));
10781 mSSData->mStateFilePath.setNull();
10782 stsFlags |= SaveSTS_StateFilePath;
10783 }
10784
10785 /* redirect to the underlying peer machine */
10786 mPeer->setMachineState (aMachineState);
10787
10788 if (aMachineState == MachineState_PoweredOff ||
10789 aMachineState == MachineState_Aborted ||
10790 aMachineState == MachineState_Saved)
10791 {
10792 /* the machine has stopped execution
10793 * (or the saved state file was adopted) */
10794 stsFlags |= SaveSTS_StateTimeStamp;
10795 }
10796
10797 if ((oldMachineState == MachineState_PoweredOff ||
10798 oldMachineState == MachineState_Aborted) &&
10799 aMachineState == MachineState_Saved)
10800 {
10801 /* the saved state file was adopted */
10802 Assert (!mSSData->mStateFilePath.isNull());
10803 stsFlags |= SaveSTS_StateFilePath;
10804 }
10805
10806 rc = saveStateSettings (stsFlags);
10807
10808 if ((oldMachineState != MachineState_PoweredOff &&
10809 oldMachineState != MachineState_Aborted) &&
10810 (aMachineState == MachineState_PoweredOff ||
10811 aMachineState == MachineState_Aborted))
10812 {
10813 /*
10814 * clear differencing hard disks based on immutable hard disks
10815 * once we've been shut down for any reason
10816 */
10817 rc = wipeOutImmutableDiffs();
10818 }
10819
10820 LogFlowThisFunc (("rc=%08X\n", rc));
10821 LogFlowThisFuncLeave();
10822 return rc;
10823}
10824
10825/**
10826 * Sends the current machine state value to the VM process.
10827 *
10828 * @note Locks this object for reading, then calls a client process.
10829 */
10830HRESULT SessionMachine::updateMachineStateOnClient()
10831{
10832 AutoCaller autoCaller (this);
10833 AssertComRCReturn (autoCaller.rc(), autoCaller.rc());
10834
10835 ComPtr <IInternalSessionControl> directControl;
10836 {
10837 AutoReadLock alock (this);
10838 AssertReturn (!!mData, E_FAIL);
10839 directControl = mData->mSession.mDirectControl;
10840
10841 /* directControl may be already set to NULL here in #OnSessionEnd()
10842 * called too early by the direct session process while there is still
10843 * some operation (like discarding the snapshot) in progress. The client
10844 * process in this case is waiting inside Session::close() for the
10845 * "end session" process object to complete, while #uninit() called by
10846 * #checkForDeath() on the Watcher thread is waiting for the pending
10847 * operation to complete. For now, we accept this inconsitent behavior
10848 * and simply do nothing here. */
10849
10850 if (mData->mSession.mState == SessionState_Closing)
10851 return S_OK;
10852
10853 AssertReturn (!directControl.isNull(), E_FAIL);
10854 }
10855
10856 return directControl->UpdateMachineState (mData->mMachineState);
10857}
10858
10859/* static */
10860DECLCALLBACK(int) SessionMachine::taskHandler (RTTHREAD thread, void *pvUser)
10861{
10862 AssertReturn (pvUser, VERR_INVALID_POINTER);
10863
10864 Task *task = static_cast <Task *> (pvUser);
10865 task->handler();
10866
10867 // it's our responsibility to delete the task
10868 delete task;
10869
10870 return 0;
10871}
10872
10873/////////////////////////////////////////////////////////////////////////////
10874// SnapshotMachine class
10875/////////////////////////////////////////////////////////////////////////////
10876
10877DEFINE_EMPTY_CTOR_DTOR (SnapshotMachine)
10878
10879HRESULT SnapshotMachine::FinalConstruct()
10880{
10881 LogFlowThisFunc (("\n"));
10882
10883 /* set the proper type to indicate we're the SnapshotMachine instance */
10884 unconst (mType) = IsSnapshotMachine;
10885
10886 return S_OK;
10887}
10888
10889void SnapshotMachine::FinalRelease()
10890{
10891 LogFlowThisFunc (("\n"));
10892
10893 uninit();
10894}
10895
10896/**
10897 * Initializes the SnapshotMachine object when taking a snapshot.
10898 *
10899 * @param aSessionMachine machine to take a snapshot from
10900 * @param aSnapshotId snapshot ID of this snapshot machine
10901 * @param aStateFilePath file where the execution state will be later saved
10902 * (or NULL for the offline snapshot)
10903 *
10904 * @note The aSessionMachine must be locked for writing.
10905 */
10906HRESULT SnapshotMachine::init (SessionMachine *aSessionMachine,
10907 INPTR GUIDPARAM aSnapshotId,
10908 INPTR BSTR aStateFilePath)
10909{
10910 LogFlowThisFuncEnter();
10911 LogFlowThisFunc (("mName={%ls}\n", aSessionMachine->mUserData->mName.raw()));
10912
10913 AssertReturn (aSessionMachine && !Guid (aSnapshotId).isEmpty(), E_INVALIDARG);
10914
10915 /* Enclose the state transition NotReady->InInit->Ready */
10916 AutoInitSpan autoInitSpan (this);
10917 AssertReturn (autoInitSpan.isOk(), E_UNEXPECTED);
10918
10919 AssertReturn (aSessionMachine->isWriteLockOnCurrentThread(), E_FAIL);
10920
10921 mSnapshotId = aSnapshotId;
10922
10923 /* memorize the primary Machine instance (i.e. not SessionMachine!) */
10924 unconst (mPeer) = aSessionMachine->mPeer;
10925 /* share the parent pointer */
10926 unconst (mParent) = mPeer->mParent;
10927
10928 /* take the pointer to Data to share */
10929 mData.share (mPeer->mData);
10930 /*
10931 * take the pointer to UserData to share
10932 * (our UserData must always be the same as Machine's data)
10933 */
10934 mUserData.share (mPeer->mUserData);
10935 /* make a private copy of all other data (recent changes from SessionMachine) */
10936 mHWData.attachCopy (aSessionMachine->mHWData);
10937 mHDData.attachCopy (aSessionMachine->mHDData);
10938
10939 /* SSData is always unique for SnapshotMachine */
10940 mSSData.allocate();
10941 mSSData->mStateFilePath = aStateFilePath;
10942
10943 /*
10944 * create copies of all shared folders (mHWData after attiching a copy
10945 * contains just references to original objects)
10946 */
10947 for (HWData::SharedFolderList::iterator it = mHWData->mSharedFolders.begin();
10948 it != mHWData->mSharedFolders.end();
10949 ++ it)
10950 {
10951 ComObjPtr <SharedFolder> folder;
10952 folder.createObject();
10953 HRESULT rc = folder->initCopy (this, *it);
10954 CheckComRCReturnRC (rc);
10955 *it = folder;
10956 }
10957
10958 /* create all other child objects that will be immutable private copies */
10959
10960 unconst (mBIOSSettings).createObject();
10961 mBIOSSettings->initCopy (this, mPeer->mBIOSSettings);
10962
10963#ifdef VBOX_WITH_VRDP
10964 unconst (mVRDPServer).createObject();
10965 mVRDPServer->initCopy (this, mPeer->mVRDPServer);
10966#endif
10967
10968 unconst (mDVDDrive).createObject();
10969 mDVDDrive->initCopy (this, mPeer->mDVDDrive);
10970
10971 unconst (mFloppyDrive).createObject();
10972 mFloppyDrive->initCopy (this, mPeer->mFloppyDrive);
10973
10974 unconst (mAudioAdapter).createObject();
10975 mAudioAdapter->initCopy (this, mPeer->mAudioAdapter);
10976
10977 unconst (mUSBController).createObject();
10978 mUSBController->initCopy (this, mPeer->mUSBController);
10979
10980 unconst (mSATAController).createObject();
10981 mSATAController->initCopy (this, mPeer->mSATAController);
10982
10983 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
10984 {
10985 unconst (mNetworkAdapters [slot]).createObject();
10986 mNetworkAdapters [slot]->initCopy (this, mPeer->mNetworkAdapters [slot]);
10987 }
10988
10989 for (ULONG slot = 0; slot < ELEMENTS (mSerialPorts); slot ++)
10990 {
10991 unconst (mSerialPorts [slot]).createObject();
10992 mSerialPorts [slot]->initCopy (this, mPeer->mSerialPorts [slot]);
10993 }
10994
10995 for (ULONG slot = 0; slot < ELEMENTS (mParallelPorts); slot ++)
10996 {
10997 unconst (mParallelPorts [slot]).createObject();
10998 mParallelPorts [slot]->initCopy (this, mPeer->mParallelPorts [slot]);
10999 }
11000
11001 /* Confirm a successful initialization when it's the case */
11002 autoInitSpan.setSucceeded();
11003
11004 LogFlowThisFuncLeave();
11005 return S_OK;
11006}
11007
11008/**
11009 * Initializes the SnapshotMachine object when loading from the settings file.
11010 *
11011 * @param aMachine machine the snapshot belngs to
11012 * @param aHWNode <Hardware> node
11013 * @param aHDAsNode <HardDiskAttachments> node
11014 * @param aSnapshotId snapshot ID of this snapshot machine
11015 * @param aStateFilePath file where the execution state is saved
11016 * (or NULL for the offline snapshot)
11017 *
11018 * @note Doesn't lock anything.
11019 */
11020HRESULT SnapshotMachine::init (Machine *aMachine,
11021 const settings::Key &aHWNode,
11022 const settings::Key &aHDAsNode,
11023 INPTR GUIDPARAM aSnapshotId, INPTR BSTR aStateFilePath)
11024{
11025 LogFlowThisFuncEnter();
11026 LogFlowThisFunc (("mName={%ls}\n", aMachine->mUserData->mName.raw()));
11027
11028 AssertReturn (aMachine && !aHWNode.isNull() && !aHDAsNode.isNull() &&
11029 !Guid (aSnapshotId).isEmpty(),
11030 E_INVALIDARG);
11031
11032 /* Enclose the state transition NotReady->InInit->Ready */
11033 AutoInitSpan autoInitSpan (this);
11034 AssertReturn (autoInitSpan.isOk(), E_UNEXPECTED);
11035
11036 /* Don't need to lock aMachine when VirtualBox is starting up */
11037
11038 mSnapshotId = aSnapshotId;
11039
11040 /* memorize the primary Machine instance */
11041 unconst (mPeer) = aMachine;
11042 /* share the parent pointer */
11043 unconst (mParent) = mPeer->mParent;
11044
11045 /* take the pointer to Data to share */
11046 mData.share (mPeer->mData);
11047 /*
11048 * take the pointer to UserData to share
11049 * (our UserData must always be the same as Machine's data)
11050 */
11051 mUserData.share (mPeer->mUserData);
11052 /* allocate private copies of all other data (will be loaded from settings) */
11053 mHWData.allocate();
11054 mHDData.allocate();
11055
11056 /* SSData is always unique for SnapshotMachine */
11057 mSSData.allocate();
11058 mSSData->mStateFilePath = aStateFilePath;
11059
11060 /* create all other child objects that will be immutable private copies */
11061
11062 unconst (mBIOSSettings).createObject();
11063 mBIOSSettings->init (this);
11064
11065#ifdef VBOX_WITH_VRDP
11066 unconst (mVRDPServer).createObject();
11067 mVRDPServer->init (this);
11068#endif
11069
11070 unconst (mDVDDrive).createObject();
11071 mDVDDrive->init (this);
11072
11073 unconst (mFloppyDrive).createObject();
11074 mFloppyDrive->init (this);
11075
11076 unconst (mAudioAdapter).createObject();
11077 mAudioAdapter->init (this);
11078
11079 unconst (mUSBController).createObject();
11080 mUSBController->init (this);
11081
11082 unconst (mSATAController).createObject();
11083 mSATAController->init (this);
11084
11085 for (ULONG slot = 0; slot < ELEMENTS (mNetworkAdapters); slot ++)
11086 {
11087 unconst (mNetworkAdapters [slot]).createObject();
11088 mNetworkAdapters [slot]->init (this, slot);
11089 }
11090
11091 for (ULONG slot = 0; slot < ELEMENTS (mSerialPorts); slot ++)
11092 {
11093 unconst (mSerialPorts [slot]).createObject();
11094 mSerialPorts [slot]->init (this, slot);
11095 }
11096
11097 for (ULONG slot = 0; slot < ELEMENTS (mParallelPorts); slot ++)
11098 {
11099 unconst (mParallelPorts [slot]).createObject();
11100 mParallelPorts [slot]->init (this, slot);
11101 }
11102
11103 /* load hardware and harddisk settings */
11104
11105 HRESULT rc = loadHardware (aHWNode);
11106 if (SUCCEEDED (rc))
11107 rc = loadHardDisks (aHDAsNode, true /* aRegistered */, &mSnapshotId);
11108
11109 if (SUCCEEDED (rc))
11110 {
11111 /* commit all changes made during the initialization */
11112 commit();
11113 }
11114
11115 /* Confirm a successful initialization when it's the case */
11116 if (SUCCEEDED (rc))
11117 autoInitSpan.setSucceeded();
11118
11119 LogFlowThisFuncLeave();
11120 return rc;
11121}
11122
11123/**
11124 * Uninitializes this SnapshotMachine object.
11125 */
11126void SnapshotMachine::uninit()
11127{
11128 LogFlowThisFuncEnter();
11129
11130 /* Enclose the state transition Ready->InUninit->NotReady */
11131 AutoUninitSpan autoUninitSpan (this);
11132 if (autoUninitSpan.uninitDone())
11133 return;
11134
11135 uninitDataAndChildObjects();
11136
11137 /* free the essential data structure last */
11138 mData.free();
11139
11140 unconst (mParent).setNull();
11141 unconst (mPeer).setNull();
11142
11143 LogFlowThisFuncLeave();
11144}
11145
11146// util::Lockable interface
11147////////////////////////////////////////////////////////////////////////////////
11148
11149/**
11150 * Overrides VirtualBoxBase::lockHandle() in order to share the lock handle
11151 * with the primary Machine instance (mPeer).
11152 */
11153RWLockHandle *SnapshotMachine::lockHandle() const
11154{
11155 AssertReturn (!mPeer.isNull(), NULL);
11156 return mPeer->lockHandle();
11157}
11158
11159// public methods only for internal purposes
11160////////////////////////////////////////////////////////////////////////////////
11161
11162/**
11163 * Called by the snapshot object associated with this SnapshotMachine when
11164 * snapshot data such as name or description is changed.
11165 *
11166 * @note Locks this object for writing.
11167 */
11168HRESULT SnapshotMachine::onSnapshotChange (Snapshot *aSnapshot)
11169{
11170 AutoWriteLock alock (this);
11171
11172 mPeer->saveSnapshotSettings (aSnapshot, SaveSS_UpdateAttrsOp);
11173
11174 /* inform callbacks */
11175 mParent->onSnapshotChange (mData->mUuid, aSnapshot->data().mId);
11176
11177 return S_OK;
11178}
11179
11180
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