VirtualBox

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

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

Main (Guest Properties): properly initialise mGuestPropertyNotificationPattern to make vmCreator happy

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

© 2023 Oracle
ContactPrivacy policyTerms of Use