VirtualBox

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

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

Must make sure UINT32_C() and friends are defined when stdint.h might be included by someone other than iprt/types.h.

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

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