VirtualBox

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

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

Main: Reverted r30067 because it doesn't help in case of remote sessions that closed themselves before the direct session is closed. Such clients need to ask the server about the session state anyway.

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

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