VirtualBox

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

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

Main: #3276: Simplified suspend/resume event handling in VBoxSVC.

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

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette