VirtualBox

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

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

Main (Guest Properties): another globbing fix

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

© 2023 Oracle
ContactPrivacy policyTerms of Use