VirtualBox

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

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

Main: Added Machine::AutoStateDependency smart class that helps to ensure the machine state won't unexpectedly change, in a lock-free manner.

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